org.kie.workbench.common.dmn.backend.DMNMarshallerTest.java Source code

Java tutorial

Introduction

Here is the source code for org.kie.workbench.common.dmn.backend.DMNMarshallerTest.java

Source

/*
 * Copyright 2017 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.kie.workbench.common.dmn.backend;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.math.BigDecimal;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Scanner;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.filters.StringInputStream;
import org.apache.tools.ant.util.ReaderInputStream;
import org.jboss.errai.marshalling.server.MappingContextSingleton;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kie.api.KieServices;
import org.kie.api.builder.Message;
import org.kie.api.builder.Message.Level;
import org.kie.api.builder.Results;
import org.kie.api.runtime.KieContainer;
import org.kie.dmn.api.core.DMNContext;
import org.kie.dmn.api.core.DMNDecisionResult;
import org.kie.dmn.api.core.DMNDecisionResult.DecisionEvaluationStatus;
import org.kie.dmn.api.core.DMNMessage;
import org.kie.dmn.api.core.DMNModel;
import org.kie.dmn.api.core.DMNResult;
import org.kie.dmn.api.core.DMNRuntime;
import org.kie.dmn.api.core.ast.BusinessKnowledgeModelNode;
import org.kie.dmn.backend.marshalling.v1x.DMNMarshallerFactory;
import org.kie.dmn.core.util.DMNRuntimeUtil;
import org.kie.dmn.core.util.KieHelper;
import org.kie.dmn.model.api.DRGElement;
import org.kie.dmn.model.api.DecisionTable;
import org.kie.dmn.model.api.Definitions;
import org.kie.dmn.model.api.FunctionKind;
import org.kie.dmn.model.api.Import;
import org.kie.dmn.model.api.dmndi.Bounds;
import org.kie.dmn.model.api.dmndi.Color;
import org.kie.dmn.model.api.dmndi.DMNEdge;
import org.kie.dmn.model.api.dmndi.DMNShape;
import org.kie.dmn.model.api.dmndi.DMNStyle;
import org.kie.dmn.model.api.dmndi.DiagramElement;
import org.kie.dmn.model.api.dmndi.Point;
import org.kie.dmn.model.v1_2.TDecision;
import org.kie.dmn.model.v1_2.TInputData;
import org.kie.dmn.model.v1_2.TItemDefinition;
import org.kie.dmn.model.v1_2.TTextAnnotation;
import org.kie.workbench.common.dmn.api.DMNDefinitionSet;
import org.kie.workbench.common.dmn.api.definition.HasComponentWidths;
import org.kie.workbench.common.dmn.api.definition.v1_1.Association;
import org.kie.workbench.common.dmn.api.definition.v1_1.AuthorityRequirement;
import org.kie.workbench.common.dmn.api.definition.v1_1.BusinessKnowledgeModel;
import org.kie.workbench.common.dmn.api.definition.v1_1.Context;
import org.kie.workbench.common.dmn.api.definition.v1_1.ContextEntry;
import org.kie.workbench.common.dmn.api.definition.v1_1.DMNDiagram;
import org.kie.workbench.common.dmn.api.definition.v1_1.DMNElement;
import org.kie.workbench.common.dmn.api.definition.v1_1.DMNModelInstrumentedBase;
import org.kie.workbench.common.dmn.api.definition.v1_1.DMNModelInstrumentedBase.Namespace;
import org.kie.workbench.common.dmn.api.definition.v1_1.Decision;
import org.kie.workbench.common.dmn.api.definition.v1_1.DecisionService;
import org.kie.workbench.common.dmn.api.definition.v1_1.Expression;
import org.kie.workbench.common.dmn.api.definition.v1_1.FunctionDefinition;
import org.kie.workbench.common.dmn.api.definition.v1_1.ImportDMN;
import org.kie.workbench.common.dmn.api.definition.v1_1.ImportPMML;
import org.kie.workbench.common.dmn.api.definition.v1_1.InformationItem;
import org.kie.workbench.common.dmn.api.definition.v1_1.InformationRequirement;
import org.kie.workbench.common.dmn.api.definition.v1_1.InputData;
import org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition;
import org.kie.workbench.common.dmn.api.definition.v1_1.KnowledgeRequirement;
import org.kie.workbench.common.dmn.api.definition.v1_1.KnowledgeSource;
import org.kie.workbench.common.dmn.api.definition.v1_1.LiteralExpression;
import org.kie.workbench.common.dmn.api.definition.v1_1.TextAnnotation;
import org.kie.workbench.common.dmn.api.editors.included.DMNImportTypes;
import org.kie.workbench.common.dmn.api.editors.included.PMMLDocumentMetadata;
import org.kie.workbench.common.dmn.api.graph.DMNDiagramUtils;
import org.kie.workbench.common.dmn.api.property.dmn.Id;
import org.kie.workbench.common.dmn.api.property.dmn.types.BuiltInType;
import org.kie.workbench.common.dmn.backend.common.DMNMarshallerImportsHelper;
import org.kie.workbench.common.dmn.backend.definition.v1_1.DecisionConverter;
import org.kie.workbench.common.dmn.backend.definition.v1_1.InputDataConverter;
import org.kie.workbench.common.dmn.backend.definition.v1_1.TextAnnotationConverter;
import org.kie.workbench.common.dmn.backend.producers.DMNMarshallerProducer;
import org.kie.workbench.common.stunner.backend.definition.factory.TestScopeModelFactory;
import org.kie.workbench.common.stunner.core.api.DefinitionManager;
import org.kie.workbench.common.stunner.core.backend.BackendFactoryManager;
import org.kie.workbench.common.stunner.core.backend.definition.adapter.reflect.BackendDefinitionAdapter;
import org.kie.workbench.common.stunner.core.backend.definition.adapter.reflect.BackendDefinitionSetAdapter;
import org.kie.workbench.common.stunner.core.backend.definition.adapter.reflect.BackendPropertyAdapter;
import org.kie.workbench.common.stunner.core.backend.definition.adapter.reflect.BackendPropertySetAdapter;
import org.kie.workbench.common.stunner.core.backend.service.XMLEncoderDiagramMetadataMarshaller;
import org.kie.workbench.common.stunner.core.definition.adapter.AdapterManager;
import org.kie.workbench.common.stunner.core.definition.adapter.binding.BindableAdapterUtils;
import org.kie.workbench.common.stunner.core.diagram.Diagram;
import org.kie.workbench.common.stunner.core.diagram.DiagramImpl;
import org.kie.workbench.common.stunner.core.diagram.Metadata;
import org.kie.workbench.common.stunner.core.diagram.MetadataImpl;
import org.kie.workbench.common.stunner.core.factory.graph.EdgeFactory;
import org.kie.workbench.common.stunner.core.factory.graph.ElementFactory;
import org.kie.workbench.common.stunner.core.factory.graph.GraphFactory;
import org.kie.workbench.common.stunner.core.factory.graph.NodeFactory;
import org.kie.workbench.common.stunner.core.factory.impl.EdgeFactoryImpl;
import org.kie.workbench.common.stunner.core.factory.impl.GraphFactoryImpl;
import org.kie.workbench.common.stunner.core.factory.impl.NodeFactoryImpl;
import org.kie.workbench.common.stunner.core.graph.Edge;
import org.kie.workbench.common.stunner.core.graph.Graph;
import org.kie.workbench.common.stunner.core.graph.Node;
import org.kie.workbench.common.stunner.core.graph.command.GraphCommandManager;
import org.kie.workbench.common.stunner.core.graph.command.GraphCommandManagerImpl;
import org.kie.workbench.common.stunner.core.graph.command.impl.GraphCommandFactory;
import org.kie.workbench.common.stunner.core.graph.content.Bound;
import org.kie.workbench.common.stunner.core.graph.content.definition.DefinitionSet;
import org.kie.workbench.common.stunner.core.graph.content.relationship.Child;
import org.kie.workbench.common.stunner.core.graph.content.view.Connection;
import org.kie.workbench.common.stunner.core.graph.content.view.ControlPoint;
import org.kie.workbench.common.stunner.core.graph.content.view.MagnetConnection;
import org.kie.workbench.common.stunner.core.graph.content.view.Point2D;
import org.kie.workbench.common.stunner.core.graph.content.view.View;
import org.kie.workbench.common.stunner.core.graph.content.view.ViewConnector;
import org.kie.workbench.common.stunner.core.graph.content.view.ViewImpl;
import org.kie.workbench.common.stunner.core.graph.impl.EdgeImpl;
import org.kie.workbench.common.stunner.core.registry.definition.AdapterRegistry;
import org.kie.workbench.common.stunner.core.registry.impl.DefinitionsCacheRegistry;
import org.kie.workbench.common.stunner.core.util.DefinitionUtils;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.commons.uuid.UUID;
import org.xml.sax.InputSource;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class DMNMarshallerTest {

    private static final Logger LOG = LoggerFactory.getLogger(DMNMarshallerTest.class);

    private static final String DMN_DEF_SET_ID = BindableAdapterUtils.getDefinitionSetId(DMNDefinitionSet.class);

    @Mock
    DefinitionManager definitionManager;

    @Mock
    AdapterManager adapterManager;

    @Mock
    AdapterRegistry adapterRegistry;

    @Mock
    BackendFactoryManager applicationFactoryManager;

    @Mock
    DefinitionsCacheRegistry definitionsRegistry;

    @Mock
    DMNMarshallerImportsHelper dmnMarshallerImportsHelper;

    EdgeFactory<Object> connectionEdgeFactory;
    NodeFactory<Object> viewNodeFactory;
    DefinitionUtils definitionUtils;

    GraphCommandManager commandManager;
    GraphCommandFactory commandFactory;

    GraphFactory dmnGraphFactory;

    TestScopeModelFactory testScopeModelFactory;

    @Before
    @SuppressWarnings("unchecked")
    public void setup() {
        // Graph utils.
        when(definitionManager.adapters()).thenReturn(adapterManager);
        when(adapterManager.registry()).thenReturn(adapterRegistry);
        definitionUtils = new DefinitionUtils(definitionManager, definitionsRegistry);
        testScopeModelFactory = new TestScopeModelFactory(new DMNDefinitionSet.DMNDefinitionSetBuilder().build());
        // Definition manager.
        final BackendDefinitionAdapter definitionAdapter = new BackendDefinitionAdapter(definitionUtils);
        final BackendDefinitionSetAdapter definitionSetAdapter = new BackendDefinitionSetAdapter(definitionAdapter);
        final BackendPropertySetAdapter propertySetAdapter = new BackendPropertySetAdapter();
        final BackendPropertyAdapter propertyAdapter = new BackendPropertyAdapter();
        when(adapterManager.forDefinitionSet()).thenReturn(definitionSetAdapter);
        when(adapterManager.forDefinition()).thenReturn(definitionAdapter);
        when(adapterManager.forPropertySet()).thenReturn(propertySetAdapter);
        when(adapterManager.forProperty()).thenReturn(propertyAdapter);
        when(adapterRegistry.getDefinitionSetAdapter(any(Class.class))).thenReturn(definitionSetAdapter);
        when(adapterRegistry.getDefinitionAdapter(any(Class.class))).thenReturn(definitionAdapter);
        when(adapterRegistry.getPropertySetAdapter(any(Class.class))).thenReturn(propertySetAdapter);
        when(adapterRegistry.getPropertyAdapter(any(Class.class))).thenReturn(propertyAdapter);
        commandManager = new GraphCommandManagerImpl(null, null, null);
        commandFactory = new GraphCommandFactory();
        connectionEdgeFactory = new EdgeFactoryImpl(definitionManager);
        viewNodeFactory = new NodeFactoryImpl(definitionUtils);
        dmnGraphFactory = new GraphFactoryImpl(definitionManager);
        doAnswer(invocationOnMock -> {
            String id = (String) invocationOnMock.getArguments()[0];
            return testScopeModelFactory.build(id);
        }).when(applicationFactoryManager).newDefinition(anyString());
        doAnswer(invocationOnMock -> {
            String uuid = (String) invocationOnMock.getArguments()[0];
            String id = (String) invocationOnMock.getArguments()[1];
            if (DMNDefinitionSet.class.getName().equals(id)) {
                // Emulate DMNGraphFactoryImpl, that adds a DMNDiagram to new Graphs
                // Please note this is different from the stunner jbpm test which this dmn test is based on
                Graph graph = (Graph) dmnGraphFactory.build(uuid, DMN_DEF_SET_ID);
                DMNDiagram model = new DMNDiagram();
                Node node = viewNodeFactory.build(uuid, model);
                graph.addNode(node);
                return graph;
            }
            Object model = testScopeModelFactory.accepts(id) ? testScopeModelFactory.build(id) : null;
            if (null != model) {
                Class<? extends ElementFactory> element = BackendDefinitionAdapter
                        .getGraphFactory(model.getClass());
                if (element.isAssignableFrom(NodeFactory.class)) {
                    Node node = viewNodeFactory.build(uuid, model);
                    return node;
                } else if (element.isAssignableFrom(EdgeFactory.class)) {
                    Edge edge = connectionEdgeFactory.build(uuid, model);
                    return edge;
                }
            }
            return null;
        }).when(applicationFactoryManager).newElement(anyString(), anyString());
        doAnswer(invocationOnMock -> {
            String uuid = (String) invocationOnMock.getArguments()[0];
            Class type = (Class) invocationOnMock.getArguments()[1];
            String id = BindableAdapterUtils.getGenericClassName(type);
            if (DMNDefinitionSet.class.equals(type)) {
                Graph graph = dmnGraphFactory.build(uuid, DMN_DEF_SET_ID);
                return graph;
            }
            Object model = testScopeModelFactory.accepts(id) ? testScopeModelFactory.build(id) : null;
            if (null != model) {
                Class<? extends ElementFactory> element = BackendDefinitionAdapter
                        .getGraphFactory(model.getClass());
                if (element.isAssignableFrom(NodeFactory.class)) {
                    Node node = viewNodeFactory.build(uuid, model);
                    return node;
                } else if (element.isAssignableFrom(EdgeFactory.class)) {
                    Edge edge = connectionEdgeFactory.build(uuid, model);
                    return edge;
                }
            }
            return null;
        }).when(applicationFactoryManager).newElement(anyString(), any(Class.class));
        doAnswer(invocationOnMock -> {
            String uuid = (String) invocationOnMock.getArguments()[0];
            String defSetId = (String) invocationOnMock.getArguments()[1];
            final Graph graph = (Graph) applicationFactoryManager.newElement(uuid, defSetId);
            final DiagramImpl result = new DiagramImpl(uuid,
                    new MetadataImpl.MetadataImplBuilder(defSetId).build());
            result.setGraph(graph);
            return result;
        }).when(applicationFactoryManager).newDiagram(anyString(), anyString(), any(Metadata.class));
    }

    /**
     * Two issues bellow prevents us from running marshalling tests on IBM jdk
     * https://support.oracle.com/knowledge/Middleware/1459269_1.html
     * https://www-01.ibm.com/support/docview.wss?uid=swg1PK99682
     */
    @Before
    public void doNotRunTestsOnIbmJdk() {
        final String ibmVendorName = "IBM";
        final String javaVendorPropertyKey = "java.vendor";
        Assume.assumeFalse(
                StringUtils.containsIgnoreCase(System.getProperty(javaVendorPropertyKey), ibmVendorName));
    }

    @Test
    public void testDynamicMarshallers() {
        // This is a fail-fast for Errai's dynamic marshallers. It isn't needed as such for
        // testing DMNMarshaller however issues with Errai's dynamic marshaller generation will
        // otherwise be reported when compiling the webapp.
        MappingContextSingleton.loadDynamicMarshallers();
    }

    @Test
    public void testLoan() throws IOException {
        roundTripUnmarshalMarshalThenUnmarshalDMN(getClass().getResourceAsStream("/Loan Pre-Qualification.dmn"));
        DMNMarshaller m = getDMNMarshaller();
        Graph<?, ?> g = m.unmarshall(null, this.getClass().getResourceAsStream("/Loan Pre-Qualification.dmn"));
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(g);
        String mString = m.marshall(diagram);
        final KieServices ks = KieServices.Factory.get();
        final KieContainer kieContainer = KieHelper.getKieContainer(
                ks.newReleaseId("org.kie", "dmn-testLoan", "1.0"),
                ks.getResources().newByteArrayResource(mString.getBytes())
                        .setTargetPath("src/main/resources/Loan Pre-Qualification.dmn"));

        final DMNRuntime runtime = kieContainer.newKieSession().getKieRuntime(DMNRuntime.class);
        Assert.assertNotNull(runtime);
        DMNModel model = runtime.getModel(
                "http://www.trisotech.com/definitions/_4e0a7f15-3176-427e-add8-68d30903c84c",
                "Loan Pre-Qualification");
        DMNContext dmnContext = runtime.newContext();
        dmnContext.set("Credit Score", new HashMap<String, Object>() {
            {
                put("FICO", 400);
            }
        });
        DMNResult dmnResult = runtime.evaluateByName(model, dmnContext, "Credit Score Rating");
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());
    }

    @Test
    public void test_DecisionTableInputOutputClausesWhenEmpty() throws IOException {
        DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/qGslQdo2.dmn"));
        Assert.assertNotNull(runtime);

        DMNModel model = runtime.getModel(
                "https://github.com/kiegroup/drools/kie-dmn/_A2C75C01-7EAD-46B8-A499-D85D6C07D273",
                "_5FE8CBFD-821B-41F6-A6C7-42BE3FC45F2F");
        Assert.assertNotNull(model);
        assertThat(model.hasErrors(), is(false));

        DMNContext dmnContext = runtime.newContext();
        dmnContext.set("my number", -99);

        DMNResult dmnResult = runtime.evaluateAll(model, dmnContext);
        LOG.debug("{}", dmnResult);
        assertThat(dmnResult.hasErrors(), is(false));
        assertThat(dmnResult.getDecisionResultByName("my decision").getEvaluationStatus(),
                is(DecisionEvaluationStatus.SUCCEEDED));
        assertThat(dmnResult.getDecisionResultByName("my decision").getResult(), is("negative"));

        org.kie.dmn.model.api.DecisionTable dmnDT = (DecisionTable) model.getDecisionByName("my decision")
                .getDecision().getExpression();
        assertThat(dmnDT.getInput().get(0).getInputValues(), nullValue()); // DROOLS-3262
        assertThat(dmnDT.getOutput().get(0).getOutputValues(), nullValue()); // DROOLS-3262
        assertThat(dmnDT.getOutput().get(0).getDefaultOutputEntry(), nullValue()); // DROOLS-3262
        assertThat(dmnDT.getOutput().get(0).getName(), nullValue()); // DROOLS-3281
    }

    @Test
    public void test_diamond() throws IOException {
        // round trip test
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/diamondDMN12.dmn"),
                this::checkDiamondGraph);

        // additionally, check the marshalled is still DMN executable as expected
        DMNMarshaller m = getDMNMarshaller();
        Graph<?, ?> g = m.unmarshall(null, this.getClass().getResourceAsStream("/diamondDMN12.dmn"));
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(g);

        String mString = m.marshall(diagram);
        final KieServices ks = KieServices.Factory.get();
        final KieContainer kieContainer = KieHelper
                .getKieContainer(ks.newReleaseId("org.kie", "dmn-test_diamond", "1.0"), ks.getResources()
                        .newByteArrayResource(mString.getBytes()).setTargetPath("src/main/resources/diamond.dmn"));

        final DMNRuntime runtime = kieContainer.newKieSession().getKieRuntime(DMNRuntime.class);
        Assert.assertNotNull(runtime);
        DMNModel diamondModel = runtime.getModel(
                "http://www.trisotech.com/definitions/_8afa6c24-55c8-43cf-8a02-fdde7fc5d1f2",
                "three decisions in a diamond shape");
        DMNContext dmnContext = runtime.newContext();
        dmnContext.set("My Name", "John Doe");
        DMNResult dmnResult = runtime.evaluateAll(diamondModel, dmnContext);
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());
        DMNContext result = dmnResult.getContext();
        assertEquals("Hello, John Doe.", result.get("My Decision"));

        // additionally, check DMN DD/DI
        org.kie.dmn.api.marshalling.DMNMarshaller dmnMarshaller = DMNMarshallerFactory.newDefaultMarshaller();
        Definitions definitions = dmnMarshaller.unmarshal(mString);

        org.kie.dmn.model.api.dmndi.DMNDiagram ddRoot = (org.kie.dmn.model.api.dmndi.DMNDiagram) definitions
                .getDMNDI().getDMNDiagram().get(0);

        DMNShape myname = findShapeByDMNI(ddRoot, "_4cd17e52-6253-41d6-820d-5824bf5197f3");
        assertBounds(500, 500, 100, 50, myname.getBounds());
        assertColor(255, 255, 255, ((DMNStyle) myname.getStyle()).getFillColor());
        assertColor(0, 0, 0, ((DMNStyle) myname.getStyle()).getStrokeColor());
        assertDMNStyle("Open Sans", 24, 255, 0, 0, (DMNStyle) myname.getStyle());

        DMNShape prefix = findShapeByDMNI(ddRoot, "_e920f38a-293c-41b8-adb3-69d0dc184fab");
        assertBounds(300, 400, 100, 50, prefix.getBounds());
        assertColor(0, 253, 25, ((DMNStyle) prefix.getStyle()).getFillColor());
        assertColor(253, 0, 0, ((DMNStyle) prefix.getStyle()).getStrokeColor());
        assertDMNStyle("Times New Roman", 8, 70, 60, 50, (DMNStyle) prefix.getStyle());

        DMNShape postfix = findShapeByDMNI(ddRoot, "_f49f9c34-29d5-4e72-91d2-f4f92117c8da");
        assertBounds(700, 400, 100, 50, postfix.getBounds());
        assertColor(247, 255, 0, ((DMNStyle) postfix.getStyle()).getFillColor());
        assertColor(0, 51, 255, ((DMNStyle) postfix.getStyle()).getStrokeColor());
        assertDMNStyle("Arial", 10, 50, 60, 70, (DMNStyle) postfix.getStyle());

        DMNShape mydecision = findShapeByDMNI(ddRoot, "_9b061fc3-8109-42e2-9fe4-fc39c90b654e");
        assertBounds(487.5, 275, 125, 75, mydecision.getBounds());
        assertColor(255, 255, 255, ((DMNStyle) mydecision.getStyle()).getFillColor());
        assertColor(0, 0, 0, ((DMNStyle) mydecision.getStyle()).getStrokeColor());
        assertDMNStyle("Monospaced", 32, 55, 66, 77, (DMNStyle) mydecision.getStyle());
    }

    private void assertDMNStyle(String fontName, double fontSize, int r, int g, int b, DMNStyle style) {
        assertEquals(fontName, style.getFontFamily());
        assertEquals(fontSize, style.getFontSize(), 0);
        assertColor(r, g, b, style.getFontColor());
    }

    private static void assertBounds(double x, double y, double width, double height, Bounds bounds) {
        assertEquals(x, bounds.getX(), 0);
        assertEquals(y, bounds.getY(), 0);
        assertEquals(width, bounds.getWidth(), 0);
        assertEquals(height, bounds.getHeight(), 0);
    }

    private static void assertColor(int r, int g, int b, Color color) {
        assertEquals(r, color.getRed());
        assertEquals(g, color.getGreen());
        assertEquals(b, color.getBlue());
    }

    private static DMNShape findShapeByDMNI(org.kie.dmn.model.api.dmndi.DMNDiagram root, String id) {
        return root.getDMNDiagramElement().stream().filter(DMNShape.class::isInstance).map(DMNShape.class::cast)
                .filter(shape -> shape.getDmnElementRef().getLocalPart().equals(id)).findFirst()
                .orElseThrow(() -> new UnsupportedOperationException(
                        "There is no DMNShape with id '" + id + "' in DMNDiagram " + root));
    }

    private static DMNEdge findEdgeByDMNI(org.kie.dmn.model.api.dmndi.DMNDiagram root, String id) {
        return root.getDMNDiagramElement().stream().filter(DMNEdge.class::isInstance).map(DMNEdge.class::cast)
                .filter(shape -> shape.getDmnElementRef().getLocalPart().equals(id)).findFirst()
                .orElseThrow(() -> new UnsupportedOperationException(
                        "There is no DMNEdge with id '" + id + "' in DMNDiagram " + root));
    }

    @Test
    public void test_potpourri_drawing() throws IOException {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/potpourri_drawing.dmn"),
                this::checkPotpourriGraph);
    }

    @Test
    public void testAssociations() throws IOException {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/associations.dmn"),
                this::checkAssociationsGraph);
    }

    @Test
    public void testTextAnnotation() throws Exception {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/textAnnotation.dmn"),
                this::checkTextAnnotationGraph);
    }

    @Test
    public void testTextAnnotationWithCDATA() throws Exception {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/textAnnotationCDATA.dmn"),
                this::checkTextAnnotationGraph);
    }

    @Test
    public void testDecisionWithContext() throws Exception {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/DecisionWithContext.dmn"),
                this::checkDecisionWithContext);
    }

    @Test
    public void testDecisionWithContextWithDefaultResult() throws Exception {
        roundTripUnmarshalThenMarshalUnmarshal(
                this.getClass().getResourceAsStream("/DecisionWithContextWithDefaultResult.dmn"),
                this::checkDecisionWithContextWithDefaultResult);
    }

    @Test
    public void testDecisionWithContextWithoutDefaultResult() throws Exception {
        roundTripUnmarshalThenMarshalUnmarshal(
                this.getClass().getResourceAsStream("/DecisionWithContextWithoutDefaultResult.dmn"),
                this::checkDecisionWithContextWithoutDefaultResult);
    }

    @Test
    public void testEdgewaypoint() throws Exception {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/edgewaypoint.dmn"),
                this::checkEdgewaypoint);
    }

    @Test
    public void test_decisionqa() throws IOException {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/decisionqa.dmn"),
                this::checkDecisionqa);
    }

    @Test
    public void test_decisionservice_1outputDecision() throws IOException {
        final DMNMarshaller m = getDMNMarshaller();
        @SuppressWarnings("unchecked")
        final Graph<?, Node<?, ?>> g = m.unmarshall(null, this.getClass().getResourceAsStream("/DROOLS-3372.dmn"));
        Node<?, ?> nodeDS = g.getNode("_659a06e2-ae80-496c-8783-f790a640bb49");
        Node<?, ?> nodeDecisionPostfix = g.getNode("_3a69915a-30af-4de3-a07f-6be514f53caa");
        moveNode(nodeDecisionPostfix, 0, -280);
        makeNodeChildOf(nodeDecisionPostfix, nodeDS);
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(g);
        String mString = m.marshall(diagram);
        LOG.debug("MARSHALLED ROUNDTRIP RESULTING xml:\n{}\n", mString);
        roundTripUnmarshalThenMarshalUnmarshal(new ReaderInputStream(new StringReader(mString)),
                this::check_decisionservice_1outputDecision);
    }

    @SuppressWarnings("unchecked")
    private void check_decisionservice_1outputDecision(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> node = graph.getNode("_659a06e2-ae80-496c-8783-f790a640bb49");
        assertNodeContentDefinitionIs(node, DecisionService.class);
        DecisionService definition = ((View<DecisionService>) node.getContent()).getDefinition();
        assertEquals(0, definition.getEncapsulatedDecision().size());
        assertEquals(0, definition.getInputData().size());
        assertEquals(1, definition.getOutputDecision().size());
        assertEquals("#" + "_3a69915a-30af-4de3-a07f-6be514f53caa",
                definition.getOutputDecision().get(0).getHref());
        assertEquals(1, definition.getInputDecision().size());
        assertEquals("#" + "_afce4fb3-9a7c-4791-bbfe-63d4b76bd61a", definition.getInputDecision().get(0).getHref());
    }

    @Test
    public void test_decisionservice_1outputDecision1encapsulatedDecision() throws IOException {
        final DMNMarshaller m = getDMNMarshaller();
        @SuppressWarnings("unchecked")
        final Graph<?, Node<?, ?>> g = m.unmarshall(null, this.getClass().getResourceAsStream("/DROOLS-3372.dmn"));
        Node<?, ?> nodeDS = g.getNode("_659a06e2-ae80-496c-8783-f790a640bb49");
        Node<?, ?> nodeDecisionPostfix = g.getNode("_3a69915a-30af-4de3-a07f-6be514f53caa");
        Node<?, ?> nodeDecisionPrefix = g.getNode("_afce4fb3-9a7c-4791-bbfe-63d4b76bd61a");
        moveNode(nodeDecisionPostfix, 0, -280);
        makeNodeChildOf(nodeDecisionPostfix, nodeDS);
        moveNode(nodeDecisionPrefix, 0, -170);
        makeNodeChildOf(nodeDecisionPrefix, nodeDS);
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(g);
        String mString = m.marshall(diagram);
        LOG.debug("MARSHALLED ROUNDTRIP RESULTING xml:\n{}\n", mString);
        roundTripUnmarshalThenMarshalUnmarshal(new ReaderInputStream(new StringReader(mString)),
                this::check_decisionservice_1outputDecision1encapsulatedDecision);
    }

    @SuppressWarnings("unchecked")
    private void check_decisionservice_1outputDecision1encapsulatedDecision(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> node = graph.getNode("_659a06e2-ae80-496c-8783-f790a640bb49");
        assertNodeContentDefinitionIs(node, DecisionService.class);
        DecisionService definition = ((View<DecisionService>) node.getContent()).getDefinition();
        assertEquals(1, definition.getEncapsulatedDecision().size());
        assertEquals("#" + "_afce4fb3-9a7c-4791-bbfe-63d4b76bd61a",
                definition.getEncapsulatedDecision().get(0).getHref());
        assertEquals(1, definition.getInputData().size());
        assertEquals("#" + "_dd5b090f-6d52-4bd8-8c7f-0c469983d44e", definition.getInputData().get(0).getHref());
        assertEquals(1, definition.getOutputDecision().size());
        assertEquals("#" + "_3a69915a-30af-4de3-a07f-6be514f53caa",
                definition.getOutputDecision().get(0).getHref());
        assertEquals(0, definition.getInputDecision().size());
    }

    @Test
    public void test_decisionservice2_1outputDecision1encapsulatedDecision() throws IOException {
        final DMNMarshaller m = getDMNMarshaller();
        @SuppressWarnings("unchecked")
        final Graph<?, Node<?, ?>> g = m.unmarshall(null,
                this.getClass().getResourceAsStream("/DROOLS-3372bis.dmn"));
        Node<?, ?> nodeDS = g.getNode("_659a06e2-ae80-496c-8783-f790a640bb49");
        Node<?, ?> nodeDecisionPostfix = g.getNode("_3a69915a-30af-4de3-a07f-6be514f53caa");
        Node<?, ?> nodeDecisionPrefix = g.getNode("_afce4fb3-9a7c-4791-bbfe-63d4b76bd61a");
        moveNode(nodeDecisionPostfix, 0, -280);
        makeNodeChildOf(nodeDecisionPostfix, nodeDS);
        moveNode(nodeDecisionPrefix, 0, -170);
        makeNodeChildOf(nodeDecisionPrefix, nodeDS);
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(g);
        String mString = m.marshall(diagram);
        LOG.debug("MARSHALLED ROUNDTRIP RESULTING xml:\n{}\n", mString);
        roundTripUnmarshalThenMarshalUnmarshal(new ReaderInputStream(new StringReader(mString)),
                this::check_decisionservice2_1outputDecision1encapsulatedDecision);
    }

    @SuppressWarnings("unchecked")
    private void check_decisionservice2_1outputDecision1encapsulatedDecision(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> node = graph.getNode("_659a06e2-ae80-496c-8783-f790a640bb49");
        assertNodeContentDefinitionIs(node, DecisionService.class);
        DecisionService definition = ((View<DecisionService>) node.getContent()).getDefinition();
        assertEquals(3, definition.getEncapsulatedDecision().size());
        assertEquals("#_ca9d65e7-a5fa-4a13-98b7-8404f4601147",
                definition.getEncapsulatedDecision().get(0).getHref());
        assertEquals("#_4b02cf97-5f9b-48ee-a4ae-229233238876",
                definition.getEncapsulatedDecision().get(1).getHref());
        assertEquals("#" + "_afce4fb3-9a7c-4791-bbfe-63d4b76bd61a",
                definition.getEncapsulatedDecision().get(2).getHref());
        assertEquals(1, definition.getInputData().size());
        assertEquals("#" + "_dd5b090f-6d52-4bd8-8c7f-0c469983d44e", definition.getInputData().get(0).getHref());
        assertEquals(3, definition.getOutputDecision().size());
        assertEquals("#_a5d0e474-083f-44ef-b00e-4ddc9a9ebd34", definition.getOutputDecision().get(0).getHref());
        assertEquals("#_8878539e-1c50-4622-b601-5878c97dc34e", definition.getOutputDecision().get(1).getHref());
        assertEquals("#" + "_3a69915a-30af-4de3-a07f-6be514f53caa",
                definition.getOutputDecision().get(2).getHref());
        assertEquals(0, definition.getInputDecision().size());
    }

    @Test
    public void test_decisionservice2_remove_1outputDecision1encapsulatedDecision() throws IOException {
        final DMNMarshaller m = getDMNMarshaller();
        @SuppressWarnings("unchecked")
        final Graph<?, Node<?, ?>> g = m.unmarshall(null,
                this.getClass().getResourceAsStream("/DROOLS-3372bis.dmn"));
        Node<?, ?> nodeDS = g.getNode("_659a06e2-ae80-496c-8783-f790a640bb49");
        Node<?, ?> nodeDecisionPostfix = g.getNode("_3a69915a-30af-4de3-a07f-6be514f53caa");
        Node<?, ?> nodeDecisionPrefix = g.getNode("_afce4fb3-9a7c-4791-bbfe-63d4b76bd61a");
        moveNode(nodeDecisionPostfix, 0, -280);
        makeNodeChildOf(nodeDecisionPostfix, nodeDS);
        moveNode(nodeDecisionPrefix, 0, -170);
        makeNodeChildOf(nodeDecisionPrefix, nodeDS);
        DiagramImpl diagram = new DiagramImpl("", null);
        Node<?, ?> nodeEncaps1 = g.getNode("_ca9d65e7-a5fa-4a13-98b7-8404f4601147");
        moveNode(nodeEncaps1, 0, +400);
        removeNodeChildOf(nodeEncaps1, nodeDS);
        Node<?, ?> nodeEncaps2 = g.getNode("_4b02cf97-5f9b-48ee-a4ae-229233238876");
        moveNode(nodeEncaps2, 0, +400);
        removeNodeChildOf(nodeEncaps2, nodeDS);
        Node<?, ?> nodeHardcoded2 = g.getNode("_8878539e-1c50-4622-b601-5878c97dc34e");
        moveNode(nodeHardcoded2, 0, +400);
        removeNodeChildOf(nodeHardcoded2, nodeDS);
        diagram.setGraph(g);
        String mString = m.marshall(diagram);
        LOG.debug("MARSHALLED ROUNDTRIP RESULTING xml:\n{}\n", mString);
        roundTripUnmarshalThenMarshalUnmarshal(new ReaderInputStream(new StringReader(mString)),
                this::check_decisionservice2_remove_1outputDecision1encapsulatedDecision);
    }

    private void removeNodeChildOf(Node<?, ?> childNode, Node<?, ?> nodeDS) {
        nodeDS.getOutEdges().removeIf(x -> {
            Edge<View<?>, ?> edge = x;
            return edge.getContent() instanceof Child && edge.getTargetNode().equals(childNode);
        });
        childNode.getInEdges().removeIf(x -> {
            Edge<View<?>, ?> edge = x;
            return edge.getContent() instanceof Child && edge.getSourceNode().equals(nodeDS);
        });
    }

    @SuppressWarnings("unchecked")
    private void check_decisionservice2_remove_1outputDecision1encapsulatedDecision(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> node = graph.getNode("_659a06e2-ae80-496c-8783-f790a640bb49");
        assertNodeContentDefinitionIs(node, DecisionService.class);
        DecisionService definition = ((View<DecisionService>) node.getContent()).getDefinition();
        assertEquals(1, definition.getEncapsulatedDecision().size());
        // no more "#_ca9d65e7-a5fa-4a13-98b7-8404f4601147"
        // no more "#_4b02cf97-5f9b-48ee-a4ae-229233238876"
        assertEquals("#" + "_afce4fb3-9a7c-4791-bbfe-63d4b76bd61a",
                definition.getEncapsulatedDecision().get(0).getHref());
        assertEquals(1, definition.getInputData().size());
        assertEquals("#" + "_dd5b090f-6d52-4bd8-8c7f-0c469983d44e", definition.getInputData().get(0).getHref());
        assertEquals(2, definition.getOutputDecision().size());
        assertEquals("#_a5d0e474-083f-44ef-b00e-4ddc9a9ebd34", definition.getOutputDecision().get(0).getHref());
        // no more "#_8878539e-1c50-4622-b601-5878c97dc34e"
        assertEquals("#" + "_3a69915a-30af-4de3-a07f-6be514f53caa",
                definition.getOutputDecision().get(1).getHref());
        assertEquals(1, definition.getInputDecision().size());
        assertEquals("#_ca9d65e7-a5fa-4a13-98b7-8404f4601147", definition.getInputDecision().get(0).getHref());
    }

    private void makeNodeChildOf(Node nodeDecisionPostfix, Node nodeDS) {
        Edge myEdge = new EdgeImpl<>(UUID.uuid());
        myEdge.setContent(new Child());
        myEdge.setSourceNode(nodeDS);
        myEdge.setTargetNode(nodeDecisionPostfix);
        nodeDS.getOutEdges().add(myEdge);
        nodeDecisionPostfix.getInEdges().add(myEdge);
    }

    private void moveNode(Node<?, ?> nodeDecisionPostfix, int dx, int dy) {
        View content = (View) nodeDecisionPostfix.getContent();
        Bound ul = content.getBounds().getUpperLeft();
        Bound lr = content.getBounds().getLowerRight();
        content.setBounds(org.kie.workbench.common.stunner.core.graph.content.Bounds.create(ul.getX() + dx,
                ul.getY() + dy, lr.getX() + dx, lr.getY() + dy));
    }

    @Test
    public void test_decisionservice3_evaluate() throws IOException {
        DMNRuntime dmnRuntime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/DROOLS-3372evaluate.dmn"));
        assertThat(dmnRuntime.getModels()).hasSize(1);
        final DMNModel dmnModel = dmnRuntime.getModels().get(0);
        final DMNContext context = dmnRuntime.newContext();
        context.set("in1", "asd");
        final DMNResult result = dmnRuntime.evaluateAll(dmnModel, context);
        assertThat(result.getDecisionResultByName("outInDS").getResult()).isEqualTo("outInDSasd");
        assertThat(result.getDecisionResultByName("out1").getResult()).isEqualTo("outInDSp1 outInDSin1");
    }

    @Test
    public void test_fontsize_stunner() throws IOException {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/test-FontSize-stunner.dmn"),
                this::checkFontsize_stunner);
    }

    @SuppressWarnings("unchecked")
    private void checkFontsize_stunner(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> node = graph.getNode("_A9D510E0-1942-4945-A945-0213EC6AAEC5");
        assertNodeContentDefinitionIs(node, InputData.class);
        InputData definition = ((View<InputData>) node.getContent()).getDefinition();
        assertEquals(Double.valueOf(21), definition.getFontSet().getFontSize().getValue());
    }

    @Test
    public void test_fontsize_sharedStyle() throws IOException {
        roundTripUnmarshalThenMarshalUnmarshal(
                this.getClass().getResourceAsStream("/test-FontSize-sharedStyle.dmn"),
                this::checkFontsize_sharedStyle);
    }

    @SuppressWarnings("unchecked")
    private void checkFontsize_sharedStyle(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> node = graph.getNode("_38b74e2e-32f8-42c5-ab51-8a3e927637e0");
        assertNodeContentDefinitionIs(node, InputData.class);
        InputData definition = ((View<InputData>) node.getContent()).getDefinition();
        assertEquals(Double.valueOf(21), definition.getFontSet().getFontSize().getValue());
    }

    @SuppressWarnings("unchecked")
    private void checkDecisionqa(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> decision = graph.getNode("_7052d0f6-ccee-462b-bd89-76afc3b6f67b");
        assertNodeContentDefinitionIs(decision, Decision.class);
        Decision decisionDefinition = ((View<Decision>) decision.getContent()).getDefinition();
        assertEquals("hardcoded question", decisionDefinition.getName().getValue());
        assertEquals("What is the codename?", decisionDefinition.getQuestion().getValue());
        assertEquals("47.", decisionDefinition.getAllowedAnswers().getValue());
    }

    @SuppressWarnings("unchecked")
    private void checkEdgewaypoint(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> decision = graph.getNode("_7647e26b-6c7c-46db-aa34-1a1a2b4d8d79");
        assertNodeContentDefinitionIs(decision, Decision.class);
        Decision decisionDefinition = ((View<Decision>) decision.getContent()).getDefinition();
        assertEquals("my decision", decisionDefinition.getName().getValue());

        Node<?, ?> inputdata = graph.getNode("_fd528e66-e2a4-4b7f-aae1-c3ca6723d0cb");
        assertNodeEdgesTo(inputdata, decision, InformationRequirement.class);

        // asserted the two Stunner graph nodes are properly connected, assert location of edge.
        List<Edge<?, ?>> outEdges = (List<Edge<?, ?>>) inputdata.getOutEdges();
        Edge<?, ?> edge = outEdges.stream().filter(e -> e.getTargetNode().equals(decision)).findFirst().get();
        ViewConnector<?> connectionContent = (ViewConnector<?>) edge.getContent();
        Point2D sourceLocation = connectionContent.getSourceConnection().get().getLocation();
        assertEquals(266.9968013763428d,
                ((View) inputdata.getContent()).getBounds().getUpperLeft().getX() + sourceLocation.getX(), 0.1d);
        assertEquals(225.99999618530273d,
                ((View) inputdata.getContent()).getBounds().getUpperLeft().getY() + sourceLocation.getY(), 0.1d);

        Point2D targetLocation = connectionContent.getTargetConnection().get().getLocation();
        assertEquals(552.2411708831787d,
                ((View) decision.getContent()).getBounds().getUpperLeft().getX() + targetLocation.getX(), 0.1d);
        assertEquals(226d, ((View) decision.getContent()).getBounds().getUpperLeft().getY() + targetLocation.getY(),
                0.1d);

        assertEquals(1, connectionContent.getControlPoints().length);
        Point2D controlPointLocation = connectionContent.getControlPoints()[0].getLocation();
        assertEquals(398.61898612976074d, controlPointLocation.getX(), 0.1d);
        assertEquals(116.99999809265137d, controlPointLocation.getY(), 0.1d);

        final Connection sourceConnection = connectionContent.getSourceConnection().get();
        assertTrue(sourceConnection instanceof MagnetConnection);
        assertTrue(((MagnetConnection) sourceConnection).isAuto());

        final Connection targetConnection = connectionContent.getTargetConnection().get();
        assertTrue(targetConnection instanceof MagnetConnection);
        assertTrue(((MagnetConnection) targetConnection).isAuto());
    }

    @SuppressWarnings("unchecked")
    private void checkTextAnnotationGraph(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> textAnnotation = graph.getNode("60915990-9E1D-42DF-B7F6-0D28383BE9D1");
        assertNodeContentDefinitionIs(textAnnotation, TextAnnotation.class);
        TextAnnotation textAnnotationDefinition = ((View<TextAnnotation>) textAnnotation.getContent())
                .getDefinition();
        assertEquals("??", textAnnotationDefinition.getDescription().getValue());
        assertEquals("<b>This Annotation holds some Long text and also UTF-8 characters</b>",
                textAnnotationDefinition.getText().getValue());
        assertEquals("text/html", textAnnotationDefinition.getTextFormat().getValue());
    }

    public void testTreeStructure() throws IOException {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/treeStructure.dmn"),
                this::checkTreeStructureGraph);
    }

    public void roundTripUnmarshalThenMarshalUnmarshal(InputStream dmnXmlInputStream,
            Consumer<Graph<?, Node<?, ?>>> checkGraphConsumer) throws IOException {
        DMNMarshaller m = getDMNMarshaller();

        // first unmarshal from DMN XML to Stunner DMN Graph
        @SuppressWarnings("unchecked")
        Graph<?, Node<?, ?>> g = m.unmarshall(null, dmnXmlInputStream);
        checkGraphConsumer.accept(g);

        // round trip to Stunner DMN Graph back to DMN XML
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(g);

        String mString = m.marshall(diagram);
        LOG.debug(mString);

        // now unmarshal once more, from the marshalled just done above, back again to Stunner DMN Graph to complete check for round-trip
        @SuppressWarnings("unchecked")
        Graph<?, Node<?, ?>> g2 = m.unmarshall(null, new StringInputStream(mString));
        checkGraphConsumer.accept(g2);
    }

    private void checkTreeStructureGraph(Graph<?, Node<?, ?>> graph) {
        Node<?, ?> root = graph.getNode("BBF2B56F-A0AF-4428-AA6A-61A655D72883");
        Node<?, ?> decisionOne = graph.getNode("B7DD0DC9-7FAC-4510-9031-FFEE067CC2F5");
        Node<?, ?> decisionTwo = graph.getNode("DF84B353-A2F6-46B9-B680-EBD98F5084C8");
        Node<?, ?> decisionThree = graph.getNode("1B6EF5EB-CA09-45A5-AB03-21CD70F941AD");
        Node<?, ?> bkmRoot = graph.getNode("AD910446-56AD-49A5-99CE-F7B9C6F74E5E");
        Node<?, ?> bkmOne = graph.getNode("C1D0937E-96F4-4EAF-8A85-45B129F38E9B");
        Node<?, ?> bkmTwo = graph.getNode("47E47E51-4509-4A3B-86E9-D690F397B69C");
        Node<?, ?> annotation = graph.getNode("47C5A244-EF6D-473D-99B6-629F70A49FCC");
        Node<?, ?> knowledgeSource = graph.getNode("CFE44FA9-7309-4F28-81E9-5C1EF455B4C2");
        Node<?, ?> knowledgeSourceInput = graph.getNode("BC0D715A-ADD4-4136-AB3D-226EABC840A2");
        Node<?, ?> decisionOneInput = graph.getNode("CF65CEB9-433F-402F-A485-90AC34E2FE39");

        assertNodeEdgesTo(decisionOne, root, InformationRequirement.class);

        assertNodeEdgesTo(decisionTwo, decisionOne, InformationRequirement.class);

        assertNodeEdgesTo(decisionThree, decisionOne, InformationRequirement.class);

        assertNodeEdgesTo(bkmRoot, annotation, Association.class);

        assertNodeEdgesTo(bkmRoot, decisionOne, KnowledgeRequirement.class);

        assertNodeEdgesTo(bkmOne, bkmRoot, KnowledgeRequirement.class);

        assertNodeEdgesTo(bkmTwo, bkmRoot, KnowledgeRequirement.class);

        assertNodeEdgesTo(decisionOneInput, decisionOne, InformationRequirement.class);

        assertNodeEdgesTo(knowledgeSource, decisionOne, AuthorityRequirement.class);

        assertNodeEdgesTo(knowledgeSourceInput, knowledgeSource, AuthorityRequirement.class);
    }

    @SuppressWarnings("unchecked")
    private void checkDecisionWithContext(Graph<?, Node<?, ?>> g) {
        Node<?, ?> decisionNode = g.getNode("_30810b88-8416-4c02-8ed1-8c19b7606243");
        assertNodeContentDefinitionIs(decisionNode, Decision.class);

        Node<?, ?> rootNode = DMNMarshaller.findDMNDiagramRoot((Graph) g);
        assertNotNull(rootNode);
        assertRootNodeConnectedTo(rootNode, decisionNode);
        assertEquals("decisionNode parent is Definitions DMN root", "_153e2b47-3bd2-4db0-828c-db3fce0b3199",
                ((DMNElement) ((Decision) ((View<?>) decisionNode.getContent()).getDefinition()).getParent())
                        .getId().getValue());

        Context context = (Context) ((Decision) ((View<?>) decisionNode.getContent()).getDefinition())
                .getExpression();
        assertEquals("contextNode's parent is decisionNode", "_30810b88-8416-4c02-8ed1-8c19b7606243",
                ((DMNElement) context.getParent()).getId().getValue());

        Expression literalExpression1 = context.getContextEntry().get(0).getExpression();
        assertEquals("literalExpression1's parent-parent is contextNode", "_0f38d114-5d6e-40dd-aa9c-9f031f9b0571",
                ((DMNElement) (literalExpression1).getParent().getParent()).getId().getValue());

        Expression literalExpression2 = context.getContextEntry().get(1).getExpression();
        assertEquals("literalExpression2's parent-parent is contextNode", "_0f38d114-5d6e-40dd-aa9c-9f031f9b0571",
                ((DMNElement) (literalExpression2).getParent().getParent()).getId().getValue());
    }

    private void checkDecisionWithContextWithDefaultResult(Graph<?, Node<?, ?>> g) {
        Node<?, ?> decisionNode = g.getNode("_30810b88-8416-4c02-8ed1-8c19b7606243");
        assertNodeContentDefinitionIs(decisionNode, Decision.class);

        Context context = (Context) ((Decision) ((View<?>) decisionNode.getContent()).getDefinition())
                .getExpression();

        InformationItem defaultResultVariable = context.getContextEntry().get(1).getVariable();
        assertNull("Default result variable", defaultResultVariable);
        Expression defaultResultExpression = context.getContextEntry().get(1).getExpression();
        assertNotNull("Default result expression", defaultResultExpression);
        assertEquals("defaultResultExpression's parent-parent is contextNode",
                "_0f38d114-5d6e-40dd-aa9c-9f031f9b0571",
                ((DMNElement) (defaultResultExpression).getParent().getParent()).getId().getValue());
    }

    private void checkDecisionWithContextWithoutDefaultResult(Graph<?, Node<?, ?>> g) {
        Node<?, ?> decisionNode = g.getNode("_30810b88-8416-4c02-8ed1-8c19b7606243");
        assertNodeContentDefinitionIs(decisionNode, Decision.class);

        Context context = (Context) ((Decision) ((View<?>) decisionNode.getContent()).getDefinition())
                .getExpression();

        InformationItem defaultResultVariable = context.getContextEntry().get(1).getVariable();
        assertNull("Default result variable", defaultResultVariable);
        Expression defaultResultExpression = context.getContextEntry().get(1).getExpression();
        assertNull("Default result expression", defaultResultExpression);
    }

    @SuppressWarnings("unchecked")
    private void checkDiamondGraph(Graph<?, Node<?, ?>> g) {
        Node<?, ?> idNode = g.getNode("_4cd17e52-6253-41d6-820d-5824bf5197f3");
        assertNodeContentDefinitionIs(idNode, InputData.class);
        assertNodeEdgesTo(idNode, g.getNode("_e920f38a-293c-41b8-adb3-69d0dc184fab"), InformationRequirement.class);
        assertNodeEdgesTo(idNode, g.getNode("_f49f9c34-29d5-4e72-91d2-f4f92117c8da"), InformationRequirement.class);
        assertNodeEdgesTo(idNode, g.getNode("_9b061fc3-8109-42e2-9fe4-fc39c90b654e"), InformationRequirement.class);

        Node<?, ?> prefixDecisionNode = g.getNode("_e920f38a-293c-41b8-adb3-69d0dc184fab");
        assertNodeContentDefinitionIs(prefixDecisionNode, Decision.class);
        assertNodeEdgesTo(prefixDecisionNode, g.getNode("_9b061fc3-8109-42e2-9fe4-fc39c90b654e"),
                InformationRequirement.class);

        Node<?, ?> postfixDecisionNode = g.getNode("_f49f9c34-29d5-4e72-91d2-f4f92117c8da");
        assertNodeContentDefinitionIs(postfixDecisionNode, Decision.class);
        assertNodeEdgesTo(postfixDecisionNode, g.getNode("_9b061fc3-8109-42e2-9fe4-fc39c90b654e"),
                InformationRequirement.class);

        Node<?, ?> myDecisionNode = g.getNode("_9b061fc3-8109-42e2-9fe4-fc39c90b654e");
        assertNodeContentDefinitionIs(myDecisionNode, Decision.class);

        Node<?, ?> rootNode = DMNMarshaller.findDMNDiagramRoot((Graph) g);
        assertNotNull(rootNode);
        assertRootNodeConnectedTo(rootNode, idNode);
        assertRootNodeConnectedTo(rootNode, prefixDecisionNode);
        assertRootNodeConnectedTo(rootNode, postfixDecisionNode);
        assertRootNodeConnectedTo(rootNode, myDecisionNode);
    }

    @SuppressWarnings("unchecked")
    private void checkAssociationsGraph(Graph<?, Node<?, ?>> g) {
        Node<?, ?> inputData = g.getNode("BD168F8B-4398-4478-8BEA-E67AA5F90FAF");
        assertNodeContentDefinitionIs(inputData, InputData.class);

        Node<?, ?> decision = g.getNode("A960E2D2-FBC1-4D11-AA33-064F6A0B5CB9");
        assertNodeContentDefinitionIs(decision, Decision.class);

        Node<?, ?> knowledgeSource = g.getNode("FB99ED65-BC43-4750-999F-7FE24690845B");
        assertNodeContentDefinitionIs(knowledgeSource, KnowledgeSource.class);

        Node<?, ?> bkm = g.getNode("2F07453C-854F-436F-8378-4CFCE63BB124");
        assertNodeContentDefinitionIs(bkm, BusinessKnowledgeModel.class);

        Node<?, ?> textAnnotation = g.getNode("7F4B8130-6F3D-4A16-9F6C-01D01DA481D2");
        assertNodeContentDefinitionIs(textAnnotation, TextAnnotation.class);

        Edge fromInput = assertNodeEdgesTo(inputData, textAnnotation, Association.class);
        assertEquals("From Input",
                ((View<Association>) fromInput.getContent()).getDefinition().getDescription().getValue());

        Edge fromDecision = assertNodeEdgesTo(decision, textAnnotation, Association.class);
        assertEquals("From Decision",
                ((View<Association>) fromDecision.getContent()).getDefinition().getDescription().getValue());

        Edge fromBkm = assertNodeEdgesTo(bkm, textAnnotation, Association.class);
        assertEquals("From BKM",
                ((View<Association>) fromBkm.getContent()).getDefinition().getDescription().getValue());

        Edge fromKnowledgeSource = assertNodeEdgesTo(knowledgeSource, textAnnotation, Association.class);
        assertEquals("From Knowledge Source",
                ((View<Association>) fromKnowledgeSource.getContent()).getDefinition().getDescription().getValue());
    }

    @SuppressWarnings("unchecked")
    private void checkPotpourriGraph(Graph<?, Node<?, ?>> g) {
        Node<?, ?> _My_Input_Data = g.getNode("_My_Input_Data");
        assertNodeContentDefinitionIs(_My_Input_Data, InputData.class);
        assertNodeEdgesTo(_My_Input_Data, g.getNode("_My_Decision_1"), InformationRequirement.class);
        assertNodeEdgesTo(_My_Input_Data, g.getNode("_KS_of_Input_Data"), AuthorityRequirement.class);
        assertNodeEdgesTo(_My_Input_Data, g.getNode("_Annotation_for_Input_Data"), Association.class);

        Node<?, ?> _Annotation_for_Input_Data = g.getNode("_Annotation_for_Input_Data");
        assertNodeContentDefinitionIs(_Annotation_for_Input_Data, TextAnnotation.class);

        Node<?, ?> _KS_of_Input_Data = g.getNode("_KS_of_Input_Data");
        assertNodeContentDefinitionIs(_KS_of_Input_Data, KnowledgeSource.class);

        Node<?, ?> _KS_of_KS_of_InputData = g.getNode("_KS_of_KS_of_InputData");
        assertNodeContentDefinitionIs(_KS_of_KS_of_InputData, KnowledgeSource.class);
        assertNodeEdgesTo(_KS_of_KS_of_InputData, g.getNode("_KS_of_Input_Data"), AuthorityRequirement.class);

        Node<?, ?> _KS_of_KS_of_Decision_1 = g.getNode("_KS_of_KS_of_Decision_1");
        assertNodeContentDefinitionIs(_KS_of_KS_of_Decision_1, KnowledgeSource.class);
        assertNodeEdgesTo(_KS_of_KS_of_Decision_1, g.getNode("_KS_of_Decision_1"), AuthorityRequirement.class);

        Node<?, ?> _KS_of_Decision_1 = g.getNode("_KS_of_Decision_1");
        assertNodeContentDefinitionIs(_KS_of_Decision_1, KnowledgeSource.class);
        assertNodeEdgesTo(_KS_of_Decision_1, g.getNode("_My_Decision_1"), AuthorityRequirement.class);

        Node<?, ?> _My_Decision_2 = g.getNode("_My_Decision_2");
        assertNodeContentDefinitionIs(_My_Decision_2, Decision.class);
        assertNodeEdgesTo(_My_Decision_2, g.getNode("_KS_of_Decision_2"), AuthorityRequirement.class);
        assertNodeEdgesTo(_My_Decision_2, g.getNode("_Annotation_for_Decision_2"), Association.class);

        Node<?, ?> _KS_of_Decision_2 = g.getNode("_KS_of_Decision_2");
        assertNodeContentDefinitionIs(_KS_of_Decision_2, KnowledgeSource.class);

        Node<?, ?> _Annotation_for_Decision_2 = g.getNode("_Annotation_for_Decision_2");
        assertNodeContentDefinitionIs(_Annotation_for_Decision_2, TextAnnotation.class);

        Node<?, ?> _Annotation_for_BKM_1 = g.getNode("_Annotation_for_BKM_1");
        assertNodeContentDefinitionIs(_Annotation_for_BKM_1, TextAnnotation.class);
        assertNodeEdgesTo(_Annotation_for_BKM_1, g.getNode("_My_BKM_1_of_Decision_1"), Association.class);

        Node<?, ?> _My_BKM_1_of_Decision_1 = g.getNode("_My_BKM_1_of_Decision_1");
        assertNodeContentDefinitionIs(_My_BKM_1_of_Decision_1, BusinessKnowledgeModel.class);
        assertNodeEdgesTo(_My_BKM_1_of_Decision_1, g.getNode("_My_Decision_1"), KnowledgeRequirement.class);

        Node<?, ?> _KS_of_BKM_1 = g.getNode("_KS_of_BKM_1");
        assertNodeContentDefinitionIs(_KS_of_BKM_1, KnowledgeSource.class);
        assertNodeEdgesTo(_KS_of_BKM_1, g.getNode("_My_BKM_1_of_Decision_1"), AuthorityRequirement.class);

        Node<?, ?> _KS_of_KS_of_BKM_1 = g.getNode("_KS_of_KS_of_BKM_1");
        assertNodeContentDefinitionIs(_KS_of_KS_of_BKM_1, KnowledgeSource.class);
        assertNodeEdgesTo(_KS_of_KS_of_BKM_1, g.getNode("_KS_of_BKM_1"), AuthorityRequirement.class);

        Node<?, ?> _My_BKM_2_of_BKM_1 = g.getNode("_My_BKM_2_of_BKM_1");
        assertNodeContentDefinitionIs(_My_BKM_2_of_BKM_1, BusinessKnowledgeModel.class);
        assertNodeEdgesTo(_My_BKM_2_of_BKM_1, g.getNode("_My_BKM_1_of_Decision_1"), KnowledgeRequirement.class);

        Node<?, ?> _KS_of_BKM_2 = g.getNode("_KS_of_BKM_2");
        assertNodeContentDefinitionIs(_KS_of_BKM_2, KnowledgeSource.class);
        assertNodeEdgesTo(_KS_of_BKM_2, g.getNode("_My_BKM_2_of_BKM_1"), AuthorityRequirement.class);
        assertNodeEdgesTo(_KS_of_BKM_2, g.getNode("_Annotation_for_KS_of_BKM_2"), Association.class);

        Node<?, ?> _Annotation_for_KS_of_BKM_2 = g.getNode("_Annotation_for_KS_of_BKM_2");
        assertNodeContentDefinitionIs(_Annotation_for_KS_of_BKM_2, TextAnnotation.class);

        Node<?, ?> _My_Decision_1 = g.getNode("_My_Decision_1");
        assertNodeContentDefinitionIs(_My_Decision_1, Decision.class);

        Node<?, ?> rootNode = DMNMarshaller.findDMNDiagramRoot((Graph) g);
        assertNotNull(rootNode);
        assertRootNodeConnectedTo(rootNode, _My_Input_Data);
        assertRootNodeConnectedTo(rootNode, _Annotation_for_Input_Data);
        assertRootNodeConnectedTo(rootNode, _KS_of_Input_Data);
        assertRootNodeConnectedTo(rootNode, _KS_of_KS_of_InputData);
        assertRootNodeConnectedTo(rootNode, _KS_of_KS_of_Decision_1);
        assertRootNodeConnectedTo(rootNode, _KS_of_Decision_1);
        assertRootNodeConnectedTo(rootNode, _My_Decision_2);
        assertRootNodeConnectedTo(rootNode, _KS_of_Decision_2);
        assertRootNodeConnectedTo(rootNode, _Annotation_for_Decision_2);
        assertRootNodeConnectedTo(rootNode, _Annotation_for_BKM_1);
        assertRootNodeConnectedTo(rootNode, _My_BKM_1_of_Decision_1);
        assertRootNodeConnectedTo(rootNode, _KS_of_BKM_1);
        assertRootNodeConnectedTo(rootNode, _KS_of_KS_of_BKM_1);
        assertRootNodeConnectedTo(rootNode, _My_BKM_2_of_BKM_1);
        assertRootNodeConnectedTo(rootNode, _KS_of_BKM_2);
        assertRootNodeConnectedTo(rootNode, _Annotation_for_KS_of_BKM_2);
        assertRootNodeConnectedTo(rootNode, _My_Decision_1);
    }

    private static void assertRootNodeConnectedTo(Node<?, ?> rootNode, Node<?, ?> to) {
        @SuppressWarnings("unchecked")
        List<Edge<?, ?>> outEdges = (List<Edge<?, ?>>) rootNode.getOutEdges();
        Optional<Edge<?, ?>> optEdge = outEdges.stream().filter(e -> e.getTargetNode().equals(to)).findFirst();
        assertTrue(optEdge.isPresent());

        Edge<?, ?> edge = optEdge.get();
        assertTrue(edge.getContent() instanceof Child);

        assertTrue(to.getInEdges().contains(edge));
    }

    private static Edge<?, ?> assertNodeEdgesTo(Node<?, ?> from, Node<?, ?> to, Class<?> clazz) {
        @SuppressWarnings("unchecked")
        List<Edge<?, ?>> outEdges = (List<Edge<?, ?>>) from.getOutEdges();
        Optional<Edge<?, ?>> optEdge = outEdges.stream().filter(e -> e.getTargetNode().equals(to)).findFirst();
        assertTrue(optEdge.isPresent());

        Edge<?, ?> edge = optEdge.get();
        assertTrue(edge.getContent() instanceof View);
        assertTrue(clazz.isInstance(((View<?>) edge.getContent()).getDefinition()));

        assertTrue(to.getInEdges().contains(edge));

        ViewConnector<?> connectionContent = (ViewConnector<?>) edge.getContent();
        assertTrue(connectionContent.getSourceConnection().isPresent());
        assertTrue(connectionContent.getTargetConnection().isPresent());
        return edge;
    }

    private static void assertNodeContentDefinitionIs(Node<?, ?> node, Class<?> clazz) {
        assertTrue(node.getContent() instanceof View);
        assertTrue(clazz.isInstance(((View<?>) node.getContent()).getDefinition()));
    }

    @Test
    public void test_Simple_structured_context() throws IOException {
        final DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/Simple_structured_context.dmn"));
        DMNModel dmnModel = runtime.getModels().get(0);

        DMNContext dmnContext = runtime.newContext();
        dmnContext.set("Input Name", "John Doe");
        DMNResult dmnResult = runtime.evaluateAll(dmnModel, dmnContext);
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());
        DMNContext result = dmnResult.getContext();
        assertEquals("Hello, John Doe!", result.get("Decision Logic 1"));
    }

    private DMNRuntime roundTripUnmarshalMarshalThenUnmarshalDMN(InputStream dmnXmlInputStream) throws IOException {
        String xml = null;
        try (BufferedReader buffer = new BufferedReader(new InputStreamReader(dmnXmlInputStream))) {
            xml = buffer.lines().collect(Collectors.joining("\n"));
        } catch (Exception e) {
            throw new RuntimeException(
                    "test utily method roundTripUnmarshalMarshalThenUnmarshalDMN failed to read XML content.", e);
        }
        LOG.debug("ORIGINAL xml:\n{}\n", xml);
        final DMNRuntime runtime0 = dmnRuntimeFromDMNXML(xml);
        assertEquals(
                "The DMN XML file contains compilation error. If this was intentional use test method roundTripUnmarshalMarshalThenUnmarshalDMNexpectingErrors",
                0, runtime0.getModels().get(0).getMessages(DMNMessage.Severity.ERROR).size());

        DMNMarshaller m = getDMNMarshaller();

        // first unmarshal from DMN XML to Stunner DMN Graph
        @SuppressWarnings("unchecked")
        Graph<?, Node<?, ?>> g = m.unmarshall(null, new ReaderInputStream(new StringReader(xml)));

        // round trip to Stunner DMN Graph back to DMN XML
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(g);

        String mString = m.marshall(diagram);
        LOG.debug("MARSHALLED ROUNDTRIP RESULTING xml:\n{}\n", mString);

        // now unmarshal once more, from the marshalled just done above, into a DMNRuntime
        final DMNRuntime runtime = dmnRuntimeFromDMNXML(mString);
        assertTrue(runtime.getModels().get(0).getMessages(DMNMessage.Severity.ERROR).size() == 0);
        return runtime;
    }

    private static DMNRuntime dmnRuntimeFromDMNXML(String mString) {
        final KieServices ks = KieServices.Factory.get();
        String uuid = UUID.uuid(8);
        final KieContainer kieContainer = KieHelper.getKieContainer(ks.newReleaseId("org.kie", uuid, "1.0"),
                ks.getResources().newByteArrayResource(mString.getBytes())
                        .setTargetPath("src/main/resources/" + uuid + ".dmn"));
        final DMNRuntime runtime = kieContainer.newKieSession().getKieRuntime(DMNRuntime.class);
        assertNotNull(runtime);
        assertFalse(runtime.getModels().isEmpty());
        return runtime;
    }

    private static class ErrorsAndDMNModelAsSerialized {

        final List<Message> messages;
        final Definitions definitions;

        public ErrorsAndDMNModelAsSerialized(List<Message> messages, Definitions definitions) {
            this.messages = Collections.unmodifiableList(messages);
            this.definitions = definitions;
        }

        public boolean hasErrors() {
            return messages.stream().filter(m -> m.getLevel().equals(Level.ERROR)).count() > 0;
        }
    }

    private ErrorsAndDMNModelAsSerialized roundTripUnmarshalMarshalThenUnmarshalDMNexpectingErrors(
            InputStream dmnXmlInputStream) throws IOException {
        String xml = null;
        try (BufferedReader buffer = new BufferedReader(new InputStreamReader(dmnXmlInputStream))) {
            xml = buffer.lines().collect(Collectors.joining("\n"));
        } catch (Exception e) {
            throw new RuntimeException(
                    "test utility method roundTripUnmarshalMarshalThenUnmarshalDMN failed to read XML content.", e);
        }
        LOG.debug("ORIGINAL xml:\n{}\n", xml);
        final List<Message> messages0 = kieBuilderMessagesUsingDMNXML(xml);
        assertTrue(
                "The DMN XML content did NOT result in compilation errors and this test method expected errors to be detected. If this was intentional use test method roundTripUnmarshalMarshalThenUnmarshalDMN",
                messages0.stream().filter(m -> m.getLevel().equals(Message.Level.ERROR)).count() > 0);

        DMNMarshaller m = getDMNMarshaller();

        // first unmarshal from DMN XML to Stunner DMN Graph
        @SuppressWarnings("unchecked")
        Graph<?, Node<?, ?>> g = m.unmarshall(null, new ReaderInputStream(new StringReader(xml)));

        // round trip to Stunner DMN Graph back to DMN XML
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(g);

        String mString = m.marshall(diagram);
        LOG.debug("MARSHALLED ROUNDTRIP RESULTING xml:\n{}\n", mString);

        // now unmarshal once more, from the marshalled just done above, into a DMNRuntime
        final List<Message> result = kieBuilderMessagesUsingDMNXML(mString);
        assertTrue(
                "The DMN XML content did NOT result in compilation errors and this test method expected errors to be detected. If this was intentional use test method roundTripUnmarshalMarshalThenUnmarshalDMN",
                messages0.stream().filter(msg -> msg.getLevel().equals(Message.Level.ERROR)).count() > 0);

        Definitions definitions = DMNMarshallerFactory.newDefaultMarshaller().unmarshal(mString);
        return new ErrorsAndDMNModelAsSerialized(result, definitions);
    }

    private static List<Message> kieBuilderMessagesUsingDMNXML(String mString) {
        final KieServices ks = KieServices.Factory.get();
        String uuid = UUID.uuid(8);
        final KieContainer kieContainer = DMNRuntimeUtil.getKieContainerIgnoringErrors(
                ks.newReleaseId("org.kie", uuid, "1.0"), ks.getResources().newByteArrayResource(mString.getBytes())
                        .setTargetPath("src/main/resources/" + uuid + ".dmn"));
        Results verify = kieContainer.verify();
        List<Message> kie_messages = verify.getMessages();
        LOG.debug("{}", kie_messages);
        return kie_messages;
    }

    @Test
    public void test_relation_literal_expression() throws IOException {
        final DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/hardcoded_relation_and_literal_expression.dmn"));
        DMNModel dmnModel = runtime.getModels().get(0);

        DMNContext dmnContext = runtime.newContext();
        DMNResult dmnResult = runtime.evaluateAll(dmnModel, dmnContext);
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());

        DMNContext result = dmnResult.getContext();
        Object hardCodedRelation = result.get("hardcoded relation");
        assertNotNull(hardCodedRelation);
        assertEquals(3, ((Collection) hardCodedRelation).size());

        DMNDecisionResult adultResult = dmnResult.getDecisionResultByName("Adults");
        assertEquals(DecisionEvaluationStatus.SUCCEEDED, adultResult.getEvaluationStatus());
        assertEquals(1, ((Collection) adultResult.getResult()).size());
    }

    @Test
    public void test_invocation() throws IOException {
        final DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/hardcoded_invokation.dmn"));
        DMNModel dmnModel = runtime.getModels().get(0);

        DMNContext dmnContext = runtime.newContext();
        DMNResult dmnResult = runtime.evaluateAll(dmnModel, dmnContext);
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());

        DMNDecisionResult adultResult = dmnResult.getDecisionResultByName("hardcoded invokation");
        assertEquals(DecisionEvaluationStatus.SUCCEEDED, adultResult.getEvaluationStatus());
        assertEquals(new BigDecimal(11), adultResult.getResult());
    }

    @Test
    public void test_function_definition_and_invocation() throws IOException {
        final DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/hardcoded_function_definition.dmn"));
        DMNModel dmnModel = runtime.getModels().get(0);

        DMNContext emptyContext = runtime.newContext();
        DMNResult dmnResult = runtime.evaluateAll(dmnModel, emptyContext);
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());

        DMNDecisionResult adultResult = dmnResult.getDecisionResultByName("hardcoded decision");
        assertEquals(DecisionEvaluationStatus.SUCCEEDED, adultResult.getEvaluationStatus());
        assertEquals(47, ((BigDecimal) adultResult.getResult()).intValue());
    }

    @Test
    public void test_function_java_WB_model() throws IOException {
        final DMNMarshaller m = getDMNMarshaller();

        @SuppressWarnings("unchecked")
        final Graph<?, Node<?, ?>> g = m.unmarshall(null, this.getClass().getResourceAsStream("/DROOLS-2372.dmn"));

        final Stream<Node<?, ?>> stream = StreamSupport
                .stream(Spliterators.spliteratorUnknownSize(g.nodes().iterator(), Spliterator.ORDERED), false);
        final Optional<Decision> wbDecision = stream.filter(n -> n.getContent() instanceof ViewImpl)
                .map(n -> (ViewImpl) n.getContent()).filter(n -> n.getDefinition() instanceof Decision)
                .map(n -> (Decision) n.getDefinition()).findFirst();

        wbDecision.ifPresent(d -> {
            assertTrue(d.getExpression() instanceof FunctionDefinition);
            final FunctionDefinition wbFunction = (FunctionDefinition) d.getExpression();

            //This is what the WB expects
            assertEquals(FunctionDefinition.Kind.JAVA, wbFunction.getKind());
        });

        final DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/DROOLS-2372.dmn"));
        final DMNModel dmnModel = runtime.getModels().get(0);

        final BusinessKnowledgeModelNode bkmNode = dmnModel.getBusinessKnowledgeModels().iterator().next();
        final org.kie.dmn.model.api.FunctionDefinition dmnFunction = bkmNode.getBusinessKnowledModel()
                .getEncapsulatedLogic();
        assertEquals(FunctionKind.JAVA, dmnFunction.getKind());
    }

    @Test
    public void test_function_definition_and_invoke_in_ctx() throws IOException {
        final DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/function_definition_and_invoke_in_ctx.dmn"));
        DMNModel dmnModel = runtime.getModels().get(0);

        DMNContext emptyContext = runtime.newContext();
        DMNResult dmnResult = runtime.evaluateAll(dmnModel, emptyContext);
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());

        DMNDecisionResult adultResult = dmnResult.getDecisionResultByName("hardcoded decision");
        assertEquals(DecisionEvaluationStatus.SUCCEEDED, adultResult.getEvaluationStatus());
        assertEquals(3, ((BigDecimal) adultResult.getResult()).intValue());
    }

    @Test
    public void test_hardcoded_decision_a_function() throws IOException {
        final DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/hardcoded_decision_a_function.dmn"));
        DMNModel dmnModel = runtime.getModels().get(0);

        DMNContext emptyContext = runtime.newContext();
        DMNResult dmnResult = runtime.evaluateAll(dmnModel, emptyContext);
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());

        DMNDecisionResult adultResult = dmnResult.getDecisionResultByName("hardcoded invokation");
        assertEquals(DecisionEvaluationStatus.SUCCEEDED, adultResult.getEvaluationStatus());
        assertEquals(3, ((BigDecimal) adultResult.getResult()).intValue());
    }

    @Test
    public void test_decision_table() throws IOException {
        final DMNRuntime runtime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/positive_or_negative.dmn"));
        DMNModel dmnModel = runtime.getModels().get(0);

        checkDecisionTableForPositiveOrNegative(runtime, dmnModel, 47, "positive");
        checkDecisionTableForPositiveOrNegative(runtime, dmnModel, -1, "negative");
    }

    private void checkDecisionTableForPositiveOrNegative(final DMNRuntime runtime, DMNModel dmnModel, int number,
            String result) {
        DMNContext context = runtime.newContext();
        context.set("a number", number);
        DMNResult dmnResult = runtime.evaluateAll(dmnModel, context);
        assertFalse(dmnResult.getMessages().toString(), dmnResult.hasErrors());

        DMNDecisionResult adultResult = dmnResult.getDecisionResultByName("positive or negative");
        assertEquals(DecisionEvaluationStatus.SUCCEEDED, adultResult.getEvaluationStatus());
        assertEquals(result, adultResult.getResult());
    }

    @Test
    //https://issues.jboss.org/browse/DROOLS-4107
    public void test_decision_table_UnaryTestsParenthood() throws IOException {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/positive_or_negative.dmn"),
                this::checkDecisionRuleInputUnaryTests);
    }

    @SuppressWarnings("unchecked")
    private void checkDecisionRuleInputUnaryTests(final Graph<?, Node<?, ?>> g) {
        final Node<?, ?> decisionNode = g.getNode("_f9f209df-1d64-4c27-90e9-3ad42cb47c07");
        assertNodeContentDefinitionIs(decisionNode, Decision.class);

        final Node<?, ?> rootNode = DMNMarshaller.findDMNDiagramRoot((Graph) g);
        assertNotNull(rootNode);
        assertRootNodeConnectedTo(rootNode, decisionNode);
        assertEquals("decisionNode parent is Definitions DMN root", "_f330b756-e84e-401d-ac3a-2e72e710d4ed",
                ((DMNElement) ((Decision) ((View<?>) decisionNode.getContent()).getDefinition()).getParent())
                        .getId().getValue());

        final org.kie.workbench.common.dmn.api.definition.v1_1.DecisionTable dtable = (org.kie.workbench.common.dmn.api.definition.v1_1.DecisionTable) ((Decision) ((View<?>) decisionNode
                .getContent()).getDefinition()).getExpression();
        dtable.getRule().forEach(rule -> rule.getInputEntry().forEach(ie -> assertEquals(ie.getParent(), rule)));
        dtable.getRule().forEach(rule -> rule.getOutputEntry().forEach(ie -> assertEquals(ie.getParent(), rule)));
        dtable.getRule().forEach(rule -> assertEquals(rule.getParent(), dtable));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void test_wrong_context() throws IOException {
        // DROOLS-2217
        final ErrorsAndDMNModelAsSerialized result = roundTripUnmarshalMarshalThenUnmarshalDMNexpectingErrors(
                this.getClass().getResourceAsStream("/wrong_context.dmn"));

        // although the DMN file is schema valid but is not a valid-DMN (a context-entry value is a literal expression missing text, which is null)
        // DROOLS-3152: once roundtripped through the Stunner marshaller it will receive an empty text. (empty expression, but a LiteralExpression with an empty text child xml element)
        // this will still naturally throw some error because unable to FEEL-parse/compile an empty expression.
        assertTrue(result.hasErrors());

        // identify the error message for context-entry "ciao":
        DMNMessage m0 = (DMNMessage) result.messages.get(0);
        assertTrue("expected a message identifying the problem on a context entry for 'ciao'",
                m0.getMessage().startsWith("Error compiling FEEL expression '' for name ")); // DROOLS-3152 please notice FEEL reporting indeed an empty expression.

        org.kie.dmn.model.api.Decision d0 = (org.kie.dmn.model.api.Decision) result.definitions.getDrgElement()
                .stream().filter(d -> d.getId().equals("_653b3426-933a-4050-9568-ab2a66b43c36")).findFirst().get();
        // the identified DMN Decision is composed of a DMN Context where the first context-entry value is a literal expression missing text (text is null).
        org.kie.dmn.model.api.Context d0c = (org.kie.dmn.model.api.Context) d0.getExpression();
        org.kie.dmn.model.api.Expression contextEntryValue = d0c.getContextEntry().get(0).getExpression();
        assertTrue(contextEntryValue instanceof org.kie.dmn.model.api.LiteralExpression);
        assertEquals("", ((org.kie.dmn.model.api.LiteralExpression) contextEntryValue).getText()); // DROOLS-3152

        // -- Stunner side.
        DMNMarshaller m = getDMNMarshaller();
        Graph<?, ?> g = m.unmarshall(null, this.getClass().getResourceAsStream("/wrong_context.dmn"));

        Node<?, ?> decisionNode = g.getNode("_653b3426-933a-4050-9568-ab2a66b43c36");
        assertNodeContentDefinitionIs(decisionNode, Decision.class);
        View<Decision> view = ((View<Decision>) decisionNode.getContent());

        // the identified DMN Decision is composed of a DMN Context where the first context-entry has missing Expression.
        Context expression = (Context) view.getDefinition().getExpression();
        assertNotNull(expression.getContextEntry().get(0).getExpression()); // DROOLS-3116 empty Literal Expression is preserved
        assertEquals(LiteralExpression.class, expression.getContextEntry().get(0).getExpression().getClass());
        LiteralExpression le = (LiteralExpression) expression.getContextEntry().get(0).getExpression();
        assertEquals("", le.getText().getValue()); // DROOLS-3152
    }

    @Test
    @SuppressWarnings("unchecked")
    public void test_wrong_decision() throws IOException {
        // DROOLS-3116 empty Literal Expression to be preserved
        final ErrorsAndDMNModelAsSerialized result = roundTripUnmarshalMarshalThenUnmarshalDMNexpectingErrors(
                this.getClass().getResourceAsStream("/wrong_decision.dmn"));

        // although the DMN file is schema valid but is not a valid-DMN (a context-entry value is a literal expression missing text, which is null)
        // DROOLS-3152: once roundtripped through the Stunner marshaller it will receive an empty text. (empty expression, but a LiteralExpression with an empty text child xml element)
        // this will still naturally throw some error because unable to FEEL-parse/compile an empty epression.
        assertTrue(result.hasErrors());

        // identify the error message for the Decision with a Literal Expression decision logic missing the actual expression text.
        DMNMessage m0 = (DMNMessage) result.messages.get(0);
        assertTrue("expected a message identifying the problem on the literalExpression of 'my decision'",
                m0.getSourceId().equals("_36dd163c-4862-4308-92bf-40a998b24e39"));

        org.kie.dmn.model.api.Decision d0 = (org.kie.dmn.model.api.Decision) result.definitions.getDrgElement()
                .stream().filter(d -> d.getId().equals("_cce32679-9395-444d-a4bf-96af8ee727a0")).findFirst().get();
        // the identified DMN Decision is composed a literal expression missing text (text is null).
        org.kie.dmn.model.api.Expression d0le = d0.getExpression();
        assertTrue(d0le instanceof org.kie.dmn.model.api.LiteralExpression);
        assertEquals("", ((org.kie.dmn.model.api.LiteralExpression) d0le).getText()); // DROOLS-3152

        // -- Stunner side.
        DMNMarshaller m = getDMNMarshaller();
        Graph<?, ?> g = m.unmarshall(null, this.getClass().getResourceAsStream("/wrong_decision.dmn"));

        Node<?, ?> decisionNode = g.getNode("_cce32679-9395-444d-a4bf-96af8ee727a0");
        assertNodeContentDefinitionIs(decisionNode, Decision.class);
        View<Decision> view = ((View<Decision>) decisionNode.getContent());

        // the identified DMN Decision is composed a literal expression missing text (text is null).
        Expression expression = view.getDefinition().getExpression();
        assertNotNull(expression); // DROOLS-3116 empty Literal Expression is preserved
        assertEquals(LiteralExpression.class, expression.getClass());
        LiteralExpression le = (LiteralExpression) expression;
        assertEquals("", le.getText().getValue()); // DROOLS-3152
    }

    @Test
    public void testOtherElements() throws IOException, XPathExpressionException {
        String original = new Scanner(this.getClass().getResourceAsStream("/dummy.dmn")).useDelimiter("\\A").next();
        DMNMarshaller marshaller = getDMNMarshaller();
        DiagramImpl diagram = new DiagramImpl("", null);
        diagram.setGraph(marshaller.unmarshall(null, getClass().getResourceAsStream("/dummy.dmn")));
        String roundtripped = marshaller.marshall(diagram);
        LOG.debug(roundtripped);
        XPath xpathOriginal = namespaceAwareXPath(
                new AbstractMap.SimpleEntry<>("semantic", "http://www.omg.org/spec/DMN/20151101/dmn.xsd"),
                new AbstractMap.SimpleEntry<>("drools", "http://www.drools.org/kie/dmn/1.1"));
        XPath xpathRountripped = namespaceAwareXPath(
                new AbstractMap.SimpleEntry<>("semantic", "http://www.omg.org/spec/DMN/20180521/MODEL/"),
                new AbstractMap.SimpleEntry<>("drools", "http://www.drools.org/kie/dmn/1.2"));
        assertXPathEquals(xpathOriginal, xpathRountripped,
                "boolean(/semantic:definitions/semantic:extensionElements)", original, roundtripped);

        assertXPathEquals(xpathOriginal, xpathRountripped, "boolean(/semantic:definitions/semantic:import)",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped, "/semantic:definitions/semantic:import/@namespace",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped, "/semantic:definitions/semantic:import/@importType",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped, "/semantic:definitions/semantic:import/@locationURI",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped, "/semantic:definitions/semantic:import/@drools:name",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped,
                "/semantic:definitions/semantic:import/@drools:modelName", original, roundtripped);

        //assertXPathEquals(xpath.compile("boolean(/semantic:definitions/semantic:elementCollection)"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:elementCollection/@name"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:elementCollection/@id"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:elementCollection/@label"), original, roundtripped);
        //assertXPathEquals(xpath.compile("boolean(/semantic:definitions/semantic:elementCollection/drgElement)"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:elementCollection/semantic:drgElement/@href"), original, roundtripped);

        //assertXPathEquals(xpath.compile("boolean(/semantic:definitions/semantic:performanceIndicator)"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:performanceIndicator/@name"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:performanceIndicator/@id"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:performanceIndicator/@label"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:performanceIndicator/@URI"), original, roundtripped);
        //assertXPathEquals(xpath.compile("boolean(/semantic:definitions/semantic:performanceIndicator/semantic:impactingDecision)"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:performanceIndicator/semantic:impactingDecision/@href"), original, roundtripped);

        //assertXPathEquals(xpath.compile("boolean(/semantic:definitions/semantic:organizationUnit)"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:organizationUnit/@name"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:organizationUnit/@id"), original, roundtripped);
        //assertXPathEquals(xpath.compile("boolean(/semantic:definitions/semantic:organizationUnit/semantic:decisionMade)"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:organizationUnit/semantic:decisionMade/@href"), original, roundtripped);
        //assertXPathEquals(xpath.compile("boolean(/semantic:definitions/semantic:organizationUnit/semantic:decisionOwned)"), original, roundtripped);
        //assertXPathEquals(xpath.compile("/semantic:definitions/semantic:organizationUnit/semantic:decisionOwned/@href"), original, roundtripped);

        assertXPathEquals(xpathOriginal, xpathRountripped,
                "boolean(/semantic:definitions/semantic:knowledgeSource)", original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped, "/semantic:definitions/semantic:knowledgeSource/@name",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped, "/semantic:definitions/semantic:knowledgeSource/@id",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped,
                "boolean(/semantic:definitions/semantic:knowledgeSource/semantic:authorityRequirement)", original,
                roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped,
                "boolean(/semantic:definitions/semantic:knowledgeSource/semantic:requiredInput)", original,
                roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped,
                "/semantic:definitions/semantic:knowledgeSource/semantic:requiredInput/@href", original,
                roundtripped);

        assertXPathEquals(xpathOriginal, xpathRountripped, "boolean(/semantic:definitions/semantic:inputData)",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped, "/semantic:definitions/semantic:inputData/@id", original,
                roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped, "/semantic:definitions/semantic:inputData/@name",
                original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped,
                "boolean(/semantic:definitions/semantic:inputData/semantic:variable)", original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped,
                "/semantic:definitions/semantic:inputData/semantic:variable/@id", original, roundtripped);
        assertXPathEquals(xpathOriginal, xpathRountripped,
                "/semantic:definitions/semantic:inputData/semantic:variable/@name", original, roundtripped);

        // DMN v1.2
        String inputDataVariableTypeRefOriginal = xpathOriginal
                .compile("/semantic:definitions/semantic:inputData/semantic:variable/@typeRef")
                .evaluate(new InputSource(new StringReader(original)));
        String inputDataVariableTypeRefRoundtripped = xpathRountripped
                .compile("/semantic:definitions/semantic:inputData/semantic:variable/@typeRef")
                .evaluate(new InputSource(new StringReader(roundtripped)));
        assertEquals("feel:number", inputDataVariableTypeRefOriginal);
        assertEquals("number", inputDataVariableTypeRefRoundtripped);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testContextEntryDataType() throws Exception {
        final DMNMarshaller marshaller = getDMNMarshaller();

        final Context context = new Context();
        context.setTypeRef(BuiltInType.DATE_TIME.asQName());

        final ContextEntry contextEntry = new ContextEntry();
        final LiteralExpression literalExpression = new LiteralExpression();
        literalExpression.setTypeRef(BuiltInType.BOOLEAN.asQName());
        literalExpression.getText().setValue("feel");
        contextEntry.setExpression(literalExpression);
        context.getContextEntry().add(contextEntry);

        final Diagram<Graph, Metadata> mockedDiagram = newDiagramDecisionWithExpression(context);

        final String marshalledSource = marshaller.marshall(mockedDiagram);

        final Graph<?, Node<View, ?>> unmarshalledGraph = marshaller.unmarshall(null,
                new StringInputStream(marshalledSource));
        assertThat(unmarshalledGraph.nodes()).hasSize(2);

        checkDecisionExpression(unmarshalledGraph, context);
    }

    @Test
    public void testDefaultObjectsAreNotCreated() throws IOException {
        final DMNRuntime dmnRuntime = roundTripUnmarshalMarshalThenUnmarshalDMN(
                this.getClass().getResourceAsStream("/DROOLS-2941.dmn"));
        final List<DMNMessage> dmn_messages = dmnRuntime.getModels().get(0).getMessages();
        assertThat(dmn_messages).isEmpty();

        assertThat(dmnRuntime.getModels()).hasSize(1);
        final DMNModel dmnModel = dmnRuntime.getModels().get(0);
        final DMNContext context = dmnRuntime.newContext();
        final DMNResult result = dmnRuntime.evaluateAll(dmnModel, context);
        assertThat(result.getDecisionResultByName("A Vowel").getResult()).isEqualTo("a");
    }

    /**
     * DROOLS-3184: If the "connection source/target location is null" assume it's the centre of the shape.
     * [source/target location is null] If the connection was created from the Toolbox (i.e. add a InputData and then the Decision from it using the Decision toolbox icon).
     * <p>
     * This test re-create by hard-code the behavior of the Stunner framework "Toolbox" by instrumenting API calls to achieve the same behavior.
     */
    @SuppressWarnings("unchecked")
    @Test
    public void testStunnerConstellationButtonCausingPoint2DbeingNull() throws IOException {
        final BiConsumer<String, HasComponentWidths> hasComponentWidthsConsumer = (uuid, hcw) -> {
            /*NOP*/};

        Diagram diagram = applicationFactoryManager.newDiagram("testDiagram",
                BindableAdapterUtils.getDefinitionSetId(DMNDefinitionSet.class), null);
        Graph g = diagram.getGraph();
        Node diagramRoot = DMNMarshaller.findDMNDiagramRoot(g);
        testAugmentWithNSPrefixes(
                ((DMNDiagram) ((View<?>) diagramRoot.getContent()).getDefinition()).getDefinitions());

        org.kie.dmn.model.api.InputData dmnInputData = new TInputData();
        dmnInputData.setId("inputDataID");
        dmnInputData.setName(dmnInputData.getId());
        Node inputDataNode = new InputDataConverter(applicationFactoryManager).nodeFromDMN(dmnInputData,
                hasComponentWidthsConsumer);
        org.kie.dmn.model.api.Decision dmnDecision = new TDecision();
        dmnDecision.setId("decisionID");
        dmnDecision.setName(dmnDecision.getId());
        Node decisionNode = new DecisionConverter(applicationFactoryManager).nodeFromDMN(dmnDecision,
                hasComponentWidthsConsumer);
        g.addNode(inputDataNode);
        g.addNode(decisionNode);
        View content = (View) decisionNode.getContent();
        content.setBounds(org.kie.workbench.common.stunner.core.graph.content.Bounds.create(200, 200, 300, 250));
        final String irID = "irID";
        Edge myEdge = applicationFactoryManager
                .newElement(irID, org.kie.workbench.common.dmn.api.definition.v1_1.InformationRequirement.class)
                .asEdge();
        myEdge.setSourceNode(inputDataNode);
        myEdge.setTargetNode(decisionNode);
        inputDataNode.getOutEdges().add(myEdge);
        decisionNode.getInEdges().add(myEdge);
        ViewConnector connectionContent = (ViewConnector) myEdge.getContent();
        // DROOLS-3184: If the "connection source/target location is null" assume it's the centre of the shape.
        // keep Stunner behavior of constellation button
        connectionContent.setSourceConnection(
                MagnetConnection.Builder.atCenter(inputDataNode).setLocation(null).setAuto(true));
        connectionContent.setTargetConnection(
                MagnetConnection.Builder.atCenter(decisionNode).setLocation(null).setAuto(true));

        DMNMarshaller.connectRootWithChild(diagramRoot, inputDataNode);
        DMNMarshaller.connectRootWithChild(diagramRoot, decisionNode);

        DMNMarshaller m = getDMNMarshaller();
        String output = m.marshall(diagram);
        LOG.debug(output);

        Definitions dmnDefinitions = DMNMarshallerFactory.newDefaultMarshaller().unmarshal(output);
        DMNEdge dmndiEdge = findEdgeByDMNI(dmnDefinitions.getDMNDI().getDMNDiagram().get(0), irID);
        assertThat(dmndiEdge.getWaypoint()).hasSize(2);
        Point wpSource = dmndiEdge.getWaypoint().get(0);
        assertThat(wpSource.getX()).isEqualByComparingTo(50d);
        assertThat(wpSource.getY()).isEqualByComparingTo(25d);
        Point wpTarget = dmndiEdge.getWaypoint().get(1);
        assertThat(wpTarget.getX()).isEqualByComparingTo(250d);
        assertThat(wpTarget.getY()).isEqualByComparingTo(225d);
    }

    /**
     * DROOLS-2569 [DMN Designer] Marshalling of magnet positions -- Association DMN Edge DMNDI serialization.
     * This test re-create by hard-code the graph to simulate the behavior of the Stunner framework programmatically.
     */
    @SuppressWarnings("unchecked")
    @Test
    public void testAssociationEdgeDMNDI() throws IOException {
        final BiConsumer<String, HasComponentWidths> hasComponentWidthsConsumer = (uuid, hcw) -> {
            /*NOP*/};

        Diagram diagram = applicationFactoryManager.newDiagram("testDiagram",
                BindableAdapterUtils.getDefinitionSetId(DMNDefinitionSet.class), null);
        Graph g = diagram.getGraph();
        Node diagramRoot = DMNMarshaller.findDMNDiagramRoot(g);
        testAugmentWithNSPrefixes(
                ((DMNDiagram) ((View<?>) diagramRoot.getContent()).getDefinition()).getDefinitions());

        org.kie.dmn.model.api.InputData dmnInputData = new TInputData();
        dmnInputData.setId("inputDataID");
        dmnInputData.setName(dmnInputData.getId());
        Node inputDataNode = new InputDataConverter(applicationFactoryManager).nodeFromDMN(dmnInputData,
                hasComponentWidthsConsumer);
        org.kie.dmn.model.api.TextAnnotation dmnTextAnnotation = new TTextAnnotation();
        dmnTextAnnotation.setId("textAnnotationID");
        Node textAnnotationNode = new TextAnnotationConverter(applicationFactoryManager)
                .nodeFromDMN(dmnTextAnnotation, hasComponentWidthsConsumer);
        g.addNode(inputDataNode);
        g.addNode(textAnnotationNode);
        View content = (View) textAnnotationNode.getContent();
        content.setBounds(org.kie.workbench.common.stunner.core.graph.content.Bounds.create(200, 200, 300, 250));
        final String edgeID = "edgeID";
        final String associationID = "associationID";
        Edge myEdge = applicationFactoryManager
                .newElement(edgeID, org.kie.workbench.common.dmn.api.definition.v1_1.Association.class).asEdge();
        final View<?> edgeView = (View<?>) myEdge.getContent();
        ((Association) edgeView.getDefinition()).setId(new Id(associationID));
        myEdge.setSourceNode(inputDataNode);
        myEdge.setTargetNode(textAnnotationNode);
        inputDataNode.getOutEdges().add(myEdge);
        textAnnotationNode.getInEdges().add(myEdge);
        ViewConnector connectionContent = (ViewConnector) myEdge.getContent();
        connectionContent.setSourceConnection(MagnetConnection.Builder.atCenter(inputDataNode));
        connectionContent.setTargetConnection(MagnetConnection.Builder.atCenter(textAnnotationNode));

        DMNMarshaller.connectRootWithChild(diagramRoot, inputDataNode);
        DMNMarshaller.connectRootWithChild(diagramRoot, textAnnotationNode);

        DMNMarshaller m = getDMNMarshaller();
        String output = m.marshall(diagram);
        LOG.debug(output);

        Definitions dmnDefinitions = DMNMarshallerFactory.newDefaultMarshaller().unmarshal(output);
        assertThat(dmnDefinitions.getDMNDI().getDMNDiagram().get(0).getDMNDiagramElement().stream()
                .filter(DMNEdge.class::isInstance).count()).isEqualTo(1);
        DMNEdge dmndiEdge = findEdgeByDMNI(dmnDefinitions.getDMNDI().getDMNDiagram().get(0), associationID);
        assertThat(dmndiEdge.getWaypoint()).hasSize(2);
    }

    @Test
    public void test_ExpressionComponentWidthPersistence() throws IOException {
        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/DROOLS-2262.dmn"),
                this::checkComponentWidths);
    }

    @Test
    public void testGetImportedDrgElementsByShape() {

        final DMNMarshaller marshaller = spy(getDMNMarshaller());
        final List<org.kie.dmn.model.api.DRGElement> importedDRGElements = mock(List.class);
        final Map<Import, org.kie.dmn.model.api.Definitions> importDefinitions = mock(Map.class);
        final org.kie.dmn.model.api.Definitions dmnXml = mock(org.kie.dmn.model.api.Definitions.class);

        final org.kie.dmn.model.api.DRGElement ref1 = mock(org.kie.dmn.model.api.DRGElement.class);
        final org.kie.dmn.model.api.DRGElement ref2 = mock(org.kie.dmn.model.api.DRGElement.class);
        final org.kie.dmn.model.api.DRGElement ref3 = mock(org.kie.dmn.model.api.DRGElement.class);

        final List<DMNShape> dmnShapes = new ArrayList<>();
        final DMNShape shape1 = mock(DMNShape.class);
        final DMNShape shape2 = mock(DMNShape.class);
        final DMNShape shape3 = mock(DMNShape.class);
        dmnShapes.add(shape1);
        dmnShapes.add(shape2);
        dmnShapes.add(shape3);

        doReturn("REF1").when(marshaller).getDmnElementRef(shape1);
        doReturn("REF2").when(marshaller).getDmnElementRef(shape2);
        doReturn("REF3").when(marshaller).getDmnElementRef(shape3);

        when(dmnMarshallerImportsHelper.getImportedDRGElements(importDefinitions)).thenReturn(importedDRGElements);

        doNothing().when(marshaller).updateIDsWithAlias(any(), any());
        doReturn(Optional.of(ref1)).when(marshaller).getReference(importedDRGElements, "REF1");
        doReturn(Optional.of(ref2)).when(marshaller).getReference(importedDRGElements, "REF2");
        doReturn(Optional.of(ref3)).when(marshaller).getReference(importedDRGElements, "REF3");

        final List<DRGElement> actual = marshaller.getImportedDrgElementsByShape(dmnShapes, importDefinitions,
                dmnXml);

        assertEquals(ref1, actual.get(0));
        assertEquals(ref2, actual.get(1));
        assertEquals(ref3, actual.get(2));
    }

    @Test
    public void testGetDmnElementRef() {

        final DMNMarshaller marshaller = spy(getDMNMarshaller());
        final String expected = "localPart";
        final DMNShape shape = mock(DMNShape.class);
        final javax.xml.namespace.QName ref = mock(javax.xml.namespace.QName.class);
        when(ref.getLocalPart()).thenReturn(expected);
        when(shape.getDmnElementRef()).thenReturn(ref);

        final String actual = marshaller.getDmnElementRef(shape);

        assertEquals(expected, actual);
    }

    @Test
    public void testGetUniqueDMNShapes() {

        final DMNMarshaller marshaller = spy(getDMNMarshaller());
        final org.kie.dmn.model.api.dmndi.DMNDiagram diagram = mock(org.kie.dmn.model.api.dmndi.DMNDiagram.class);
        final List<DiagramElement> elements = new ArrayList<>();

        final DMNShape unique1 = mock(DMNShape.class);
        when(unique1.getId()).thenReturn("unique1");

        final DMNShape unique2 = mock(DMNShape.class);
        when(unique2.getId()).thenReturn("unique2");

        final DMNShape duplicate1 = mock(DMNShape.class);
        when(duplicate1.getId()).thenReturn("duplicate");

        final DMNShape duplicate2 = mock(DMNShape.class);
        when(duplicate2.getId()).thenReturn("duplicate");

        elements.add(unique1);
        elements.add(unique2);
        elements.add(duplicate1);
        elements.add(duplicate2);

        when(diagram.getDMNDiagramElement()).thenReturn(elements);

        final List<DMNShape> actual = marshaller.getUniqueDMNShapes(diagram);

        assertEquals(3, actual.size());
        assertTrue(actual.contains(unique1));
        assertTrue(actual.contains(unique2));
        assertTrue(actual.contains(duplicate1) || actual.contains(duplicate2));
    }

    @Test
    public void testSetAllowOnlyVisualChangeToTrue() {
        testSetAllowOnlyVisualChange(true);
    }

    @Test
    public void testSetAllowOnlyVisualChangeToFalse() {
        testSetAllowOnlyVisualChange(false);
    }

    private void testSetAllowOnlyVisualChange(final boolean expected) {

        final DMNMarshaller marshaller = spy(getDMNMarshaller());
        final List<org.kie.dmn.model.api.DRGElement> importedDrgElements = mock(List.class);
        final Node node = mock(Node.class);
        final org.kie.workbench.common.dmn.api.definition.v1_1.DRGElement element = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.DRGElement.class);
        doReturn(Optional.of(element)).when(marshaller).getDRGElement(node);
        doReturn(expected).when(marshaller).isImportedDRGElement(importedDrgElements, element);

        marshaller.setAllowOnlyVisualChange(importedDrgElements, node);

        verify(element).setAllowOnlyVisualChange(expected);
    }

    @Test
    public void testIsImportedDRGElementWithDmnDRGElement() {

        final DMNMarshaller marshaller = spy(getDMNMarshaller());
        final List<org.kie.dmn.model.api.DRGElement> importedDrgElements = new ArrayList<>();

        final org.kie.dmn.model.api.DRGElement imported = mock(org.kie.dmn.model.api.DRGElement.class);
        when(imported.getId()).thenReturn("id");
        importedDrgElements.add(imported);

        final org.kie.dmn.model.api.DRGElement drgElement = mock(org.kie.dmn.model.api.DRGElement.class);
        when(drgElement.getId()).thenReturn("id");

        final boolean actual = marshaller.isImportedDRGElement(importedDrgElements, drgElement);

        assertTrue(actual);
    }

    @Test
    public void testIsImportedDRGElementWithWbDRGElement() {

        final DMNMarshaller marshaller = spy(getDMNMarshaller());
        final List<org.kie.dmn.model.api.DRGElement> importedDrgElements = new ArrayList<>();

        final org.kie.dmn.model.api.DRGElement imported = mock(org.kie.dmn.model.api.DRGElement.class);
        when(imported.getId()).thenReturn("id");
        importedDrgElements.add(imported);

        final org.kie.workbench.common.dmn.api.definition.v1_1.DRGElement drgElement = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.DRGElement.class);
        final Id id = mock(Id.class);
        when(id.getValue()).thenReturn("id");
        when(drgElement.getId()).thenReturn(id);

        final boolean actual = marshaller.isImportedDRGElement(importedDrgElements, drgElement);

        assertTrue(actual);
    }

    @Test
    public void testUpdateIDsWithAlias() {

        final DMNMarshaller marshaller = getDMNMarshaller();
        final HashMap<String, String> indexByUri = new HashMap<>();
        final String namespace1 = "https://kiegroup.org/dmn/_red";
        final String namespace2 = "https://kiegroup.org/dmn/_blue";
        final String namespace3 = "https://kiegroup.org/dmn/_yellow";
        final String missingNamespace = "missing_namespace";

        final String someWrongAlias = "some wrong alias";

        final String include1 = "include1";
        final String include2 = "include2";
        final String include3 = "include3";

        final String id1 = "id1";
        final String id2 = "id2";
        final String id3 = "id3";
        final String id4 = "id4";

        indexByUri.put(namespace1, include1);
        indexByUri.put(namespace2, include2);
        indexByUri.put(namespace3, include3);

        final List<org.kie.dmn.model.api.DRGElement> importedDrgElements = new ArrayList<>();
        final DRGElement element1 = createDRGElementWithNamespaceAndId(namespace1, someWrongAlias + ":" + id1);
        importedDrgElements.add(element1);

        final DRGElement element2 = createDRGElementWithNamespaceAndId(namespace2, someWrongAlias + ":" + id2);
        importedDrgElements.add(element2);

        final DRGElement element3 = createDRGElementWithNamespaceAndId(namespace3, someWrongAlias + ":" + id3);
        importedDrgElements.add(element3);

        final DRGElement element4 = createDRGElementWithNamespaceAndId(missingNamespace, id4);
        importedDrgElements.add(element4);

        marshaller.updateIDsWithAlias(indexByUri, importedDrgElements);

        verify(element1).setId(include1 + ":" + id1);
        verify(element2).setId(include2 + ":" + id2);
        verify(element3).setId(include3 + ":" + id3);

        verify(element4, never()).setId(any());
    }

    private org.kie.dmn.model.api.DRGElement createDRGElementWithNamespaceAndId(final String namespace,
            final String id) {

        final org.kie.dmn.model.api.DRGElement drgElement = mock(DRGElement.class);
        final Map<QName, String> additionalAttributes = new HashMap<>();

        additionalAttributes.put(new QName("Namespace"), namespace);

        when(drgElement.getAdditionalAttributes()).thenReturn(additionalAttributes);
        when(drgElement.getId()).thenReturn(id);

        return drgElement;
    }

    @Test
    public void testChangeAliasForImportedElement() {

        final DMNMarshaller marshaller = getDMNMarshaller();
        final org.kie.dmn.model.api.DRGElement drgElement = mock(org.kie.dmn.model.api.DRGElement.class);
        final String alias = "include1";
        final String id = "_01234567";

        when(drgElement.getId()).thenReturn("some another alias:" + id);

        marshaller.changeAlias(alias, drgElement);

        verify(drgElement).setId(alias + ":" + id);
    }

    @Test
    public void testChangeAliasForLocalElement() {

        final DMNMarshaller marshaller = getDMNMarshaller();
        final org.kie.dmn.model.api.DRGElement drgElement = mock(org.kie.dmn.model.api.DRGElement.class);
        final String alias = "include1";
        final String id = "_01234567";

        when(drgElement.getId()).thenReturn(id);

        marshaller.changeAlias(alias, drgElement);

        verify(drgElement, never()).setId(any());
    }

    @Test
    public void testLoadImportedItemDefinitions() {

        final org.kie.workbench.common.dmn.api.definition.v1_1.Definitions definitions = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.Definitions.class);
        final org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition itemDefinition1 = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition.class);
        final org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition itemDefinition2 = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition.class);
        final List<org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition> expectedItemDefinitions = asList(
                itemDefinition1, itemDefinition2);
        final List<org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition> actualItemDefinitions = new ArrayList<>();
        final Map<Import, org.kie.dmn.model.api.Definitions> importDefinitions = new HashMap<>();
        final DMNMarshaller dmnMarshaller = spy(getDMNMarshaller());

        doReturn(expectedItemDefinitions).when(dmnMarshaller).getWbImportedItemDefinitions(importDefinitions);
        when(definitions.getItemDefinition()).thenReturn(actualItemDefinitions);

        dmnMarshaller.loadImportedItemDefinitions(definitions, importDefinitions);

        assertEquals(expectedItemDefinitions, actualItemDefinitions);
    }

    @Test
    public void testCleanImportedItemDefinitions() {

        final org.kie.workbench.common.dmn.api.definition.v1_1.Definitions definitions = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.Definitions.class);
        final org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition itemDefinition1 = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition.class);
        final org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition itemDefinition2 = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition.class);
        final org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition itemDefinition3 = mock(
                org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition.class);
        final List<org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition> actualItemDefinitions = new ArrayList<>(
                asList(itemDefinition1, itemDefinition2, itemDefinition3));
        final List<org.kie.workbench.common.dmn.api.definition.v1_1.ItemDefinition> expectedItemDefinitions = asList(
                itemDefinition1, itemDefinition3);
        final DMNMarshaller dmnMarshaller = getDMNMarshaller();

        when(itemDefinition1.isAllowOnlyVisualChange()).thenReturn(false);
        when(itemDefinition2.isAllowOnlyVisualChange()).thenReturn(true);
        when(itemDefinition3.isAllowOnlyVisualChange()).thenReturn(false);
        when(definitions.getItemDefinition()).thenReturn(actualItemDefinitions);

        dmnMarshaller.cleanImportedItemDefinitions(definitions);

        assertEquals(expectedItemDefinitions, actualItemDefinitions);
    }

    @Test
    public void testGetWbImportedItemDefinitions() {

        final org.kie.dmn.model.api.ItemDefinition itemDefinition1 = makeItemDefinition("model1.tUUID");
        final org.kie.dmn.model.api.ItemDefinition itemDefinition2 = makeItemDefinition("model1.tPerson");
        final org.kie.dmn.model.api.ItemDefinition itemDefinition3 = makeItemDefinition("model2.tNum");
        final Map<Import, org.kie.dmn.model.api.Definitions> importDefinitions = new HashMap<>();

        when(dmnMarshallerImportsHelper.getImportedItemDefinitions(importDefinitions))
                .thenReturn(asList(itemDefinition1, itemDefinition2, itemDefinition3));

        final List<ItemDefinition> actualItemDefinitions = getDMNMarshaller()
                .getWbImportedItemDefinitions(importDefinitions);

        assertEquals(3, actualItemDefinitions.size());
        assertEquals("model1.tUUID", actualItemDefinitions.get(0).getName().getValue());
        assertEquals("model1.tPerson", actualItemDefinitions.get(1).getName().getValue());
        assertEquals("model2.tNum", actualItemDefinitions.get(2).getName().getValue());
    }

    private org.kie.dmn.model.api.ItemDefinition makeItemDefinition(final String name) {
        final org.kie.dmn.model.api.ItemDefinition itemDefinition = new TItemDefinition();
        itemDefinition.setName(name);
        return itemDefinition;
    }

    @Test
    public void testConnectorRightToLeft() throws Exception {
        final org.kie.workbench.common.stunner.core.graph.content.Bounds bounds = org.kie.workbench.common.stunner.core.graph.content.Bounds
                .create(0, 0, 100, 50);
        final String decisionNode1UUID = org.kie.workbench.common.stunner.core.util.UUID.uuid();
        final String decisionNode2UUID = org.kie.workbench.common.stunner.core.util.UUID.uuid();
        final String edgeUUID = org.kie.workbench.common.stunner.core.util.UUID.uuid();

        final ViewConnector edgeView = marshallAndUnMarshallConnectors(bounds, decisionNode1UUID, decisionNode2UUID,
                edgeUUID, (sc) -> {
                    when(sc.getMagnetIndex()).thenReturn(OptionalInt.of(MagnetConnection.MAGNET_RIGHT));
                    when(sc.getLocation()).thenReturn(new Point2D(bounds.getWidth(), bounds.getHeight() / 2));
                }, (tc) -> {
                    when(tc.getMagnetIndex()).thenReturn(OptionalInt.of(MagnetConnection.MAGNET_LEFT));
                    when(tc.getLocation()).thenReturn(new Point2D(0, bounds.getHeight() / 2));
                });

        final MagnetConnection sourceConnection = (MagnetConnection) edgeView.getSourceConnection().get();
        assertEquals(bounds.getWidth(), sourceConnection.getLocation().getX(), 0.0);
        assertEquals(bounds.getHeight() / 2, sourceConnection.getLocation().getY(), 0.0);
        assertFalse(sourceConnection.getMagnetIndex().isPresent());
        assertTrue(sourceConnection.isAuto());

        final MagnetConnection targetConnection = (MagnetConnection) edgeView.getTargetConnection().get();
        assertEquals(0, targetConnection.getLocation().getX(), 0.0);
        assertEquals(bounds.getHeight() / 2, targetConnection.getLocation().getY(), 0.0);
        assertFalse(targetConnection.getMagnetIndex().isPresent());
        assertTrue(targetConnection.isAuto());
    }

    @Test
    public void testConnectorCentreToCentre() throws Exception {
        final org.kie.workbench.common.stunner.core.graph.content.Bounds bounds = org.kie.workbench.common.stunner.core.graph.content.Bounds
                .create(0, 0, 100, 50);
        final String decisionNode1UUID = org.kie.workbench.common.stunner.core.util.UUID.uuid();
        final String decisionNode2UUID = org.kie.workbench.common.stunner.core.util.UUID.uuid();
        final String edgeUUID = org.kie.workbench.common.stunner.core.util.UUID.uuid();

        final ViewConnector edgeView = marshallAndUnMarshallConnectors(bounds, decisionNode1UUID, decisionNode2UUID,
                edgeUUID, (sc) -> {
                    /*NOP*/},
                (tc) -> {
                    /*NOP*/});

        final MagnetConnection sourceConnection = (MagnetConnection) edgeView.getSourceConnection().get();
        assertEquals(bounds.getWidth() / 2, sourceConnection.getLocation().getX(), 0.0);
        assertEquals(bounds.getHeight() / 2, sourceConnection.getLocation().getY(), 0.0);
        assertTrue(sourceConnection.getMagnetIndex().isPresent());
        assertEquals(MagnetConnection.MAGNET_CENTER, sourceConnection.getMagnetIndex().getAsInt());
        assertFalse(sourceConnection.isAuto());

        final MagnetConnection targetConnection = (MagnetConnection) edgeView.getTargetConnection().get();
        assertEquals(bounds.getWidth() / 2, targetConnection.getLocation().getX(), 0.0);
        assertEquals(bounds.getHeight() / 2, targetConnection.getLocation().getY(), 0.0);
        assertTrue(targetConnection.getMagnetIndex().isPresent());
        assertEquals(MagnetConnection.MAGNET_CENTER, targetConnection.getMagnetIndex().getAsInt());
        assertFalse(targetConnection.isAuto());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testImportConversion() throws IOException {
        //Assume every import in the DMN file can be resolved to the actual file.
        when(dmnMarshallerImportsHelper.getPMMLDocuments(any(Metadata.class), anyList())).thenAnswer(i -> {
            final Map<Import, PMMLDocumentMetadata> pmmlDocuments = new HashMap<>();
            final List<Import> imports = (List<Import>) i.getArguments()[1];
            imports.forEach(imp -> pmmlDocuments.put(imp, mock(PMMLDocumentMetadata.class)));
            return pmmlDocuments;
        });

        roundTripUnmarshalThenMarshalUnmarshal(this.getClass().getResourceAsStream("/imports.dmn"),
                this::checkImports);
    }

    @SuppressWarnings("unchecked")
    private void checkImports(final Graph<?, Node<?, ?>> graph) {
        assertNotNull(graph);

        final DMNDiagramUtils utils = new DMNDiagramUtils();
        final Diagram mockDiagram = mock(Diagram.class);
        when(mockDiagram.getGraph()).thenReturn(graph);

        final org.kie.workbench.common.dmn.api.definition.v1_1.Definitions definitions = utils
                .getDefinitions(mockDiagram);
        final List<org.kie.workbench.common.dmn.api.definition.v1_1.Import> imports = definitions.getImport();
        assertTrue(imports.get(0) instanceof ImportDMN);
        assertTrue(imports.get(1) instanceof ImportPMML);

        final ImportDMN dmnImport = (ImportDMN) imports.get(0);
        assertEquals("dmn-import", dmnImport.getName().getValue());
        assertEquals("https://kiegroup.org/dmn/_46EB0D0D-7241-4629-A38E-0377AA5B32D1", dmnImport.getNamespace());
        assertEquals(DMNImportTypes.DMN.getDefaultNamespace(), dmnImport.getImportType());

        final ImportPMML pmmlImport = (ImportPMML) imports.get(1);
        assertEquals("pmml-import", pmmlImport.getName().getValue());
        assertEquals("pmml-import", pmmlImport.getNamespace());
        assertEquals(DMNImportTypes.PMML.getDefaultNamespace(), pmmlImport.getImportType());
    }

    @SuppressWarnings("unchecked")
    private ViewConnector marshallAndUnMarshallConnectors(
            final org.kie.workbench.common.stunner.core.graph.content.Bounds bounds, final String decisionNode1UUID,
            final String decisionNode2UUID, final String edgeUUID,
            final Consumer<MagnetConnection> sourceMagnetConsumer,
            final Consumer<MagnetConnection> targetMagnetConsumer) throws Exception {
        final DMNMarshaller marshaller = getDMNMarshaller();

        final Diagram<Graph, Metadata> mockedDiagram = connectTwoNodes(bounds, decisionNode1UUID, decisionNode2UUID,
                edgeUUID, sourceMagnetConsumer, targetMagnetConsumer);

        final String marshalledSource = marshaller.marshall(mockedDiagram);

        final Graph<?, Node<View, ?>> unmarshalledGraph = marshaller.unmarshall(null,
                new StringInputStream(marshalledSource));

        assertNotNull(unmarshalledGraph);

        final Node<?, ?> decision1Node = unmarshalledGraph.getNode(decisionNode1UUID);
        final Node<?, ?> decision2Node = unmarshalledGraph.getNode(decisionNode2UUID);
        assertNotNull(decision1Node);
        assertNotNull(decision2Node);
        assertEquals(1, decision1Node.getOutEdges().size());
        assertEquals(2, decision2Node.getInEdges().size());

        final Edge decision1NodeOutEdge = decision1Node.getOutEdges().get(0);
        final Edge decision2NodeInEdge = decision2Node.getInEdges().get(0);
        assertEquals(decision1NodeOutEdge, decision2NodeInEdge);

        final ViewConnector edgeView = (ViewConnector) decision1NodeOutEdge.getContent();
        assertTrue(edgeView.getSourceConnection().isPresent());
        assertTrue(edgeView.getTargetConnection().isPresent());

        return edgeView;
    }

    @SuppressWarnings("unchecked")
    private Diagram<Graph, Metadata> connectTwoNodes(
            final org.kie.workbench.common.stunner.core.graph.content.Bounds bounds, final String decisionNode1UUID,
            final String decisionNode2UUID, final String edgeUUID,
            final Consumer<MagnetConnection> sourceMagnetConsumer,
            final Consumer<MagnetConnection> targetMagnetConsumer) {
        final DiagramImpl diagram = new DiagramImpl("dmn graph", null);
        final Graph<DefinitionSet, Node> graph = mock(Graph.class);

        final Node<View, Edge> diagramNode = mock(Node.class);
        final View diagramView = mock(View.class);
        final DMNDiagram dmnDiagram = new DMNDiagram();
        when(diagramNode.getContent()).thenReturn(diagramView);
        when(diagramView.getDefinition()).thenReturn(dmnDiagram);

        final Node<View, Edge> decisionNode1 = mock(Node.class);
        final View decisionView1 = mock(View.class);
        final Decision decision1 = new Decision();
        decision1.getId().setValue(decisionNode1UUID);
        when(decisionNode1.getContent()).thenReturn(decisionView1);
        when(decisionView1.getDefinition()).thenReturn(decision1);
        when(decisionView1.getBounds()).thenReturn(bounds);

        final Node<View, Edge> decisionNode2 = mock(Node.class);
        final View decisionView2 = mock(View.class);
        final Decision decision2 = new Decision();
        decision2.getId().setValue(decisionNode2UUID);
        when(decisionNode2.getContent()).thenReturn(decisionView2);
        when(decisionView2.getDefinition()).thenReturn(decision2);
        when(decisionView2.getBounds()).thenReturn(bounds);

        final Edge edge = mock(Edge.class);
        final ViewConnector edgeView = mock(ViewConnector.class);
        when(edge.getUUID()).thenReturn(edgeUUID);
        when(edge.getContent()).thenReturn(edgeView);
        final MagnetConnection edgeSourceConnection = mock(MagnetConnection.class);
        final MagnetConnection edgeTargetConnection = mock(MagnetConnection.class);
        when(edgeView.getSourceConnection()).thenReturn(Optional.of(edgeSourceConnection));
        when(edgeView.getTargetConnection()).thenReturn(Optional.of(edgeTargetConnection));
        when(edgeView.getControlPoints()).thenReturn(new ControlPoint[] {});
        when(decisionNode1.getOutEdges()).thenReturn(Collections.singletonList(edge));
        when(decisionNode2.getInEdges()).thenReturn(Collections.singletonList(edge));
        when(edge.getSourceNode()).thenReturn(decisionNode1);
        when(edge.getTargetNode()).thenReturn(decisionNode2);

        sourceMagnetConsumer.accept(edgeSourceConnection);
        targetMagnetConsumer.accept(edgeTargetConnection);

        doReturn(asList(diagramNode, decisionNode1, decisionNode2)).when(graph).nodes();
        diagram.setGraph(graph);

        return diagram;
    }

    @SuppressWarnings("unchecked")
    private void checkComponentWidths(Graph<?, Node<?, ?>> graph) {
        final Node<?, ?> node = graph.getNode("_37883BDC-DB54-4925-B539-A0F19B1DDE41");
        assertThat(node).isNotNull();
        assertNodeContentDefinitionIs(node, Decision.class);

        final Decision definition = ((View<Decision>) node.getContent()).getDefinition();
        assertThat(definition.getExpression()).isNotNull();

        final HasComponentWidths expression = definition.getExpression();
        final List<Double> componentWidths = expression.getComponentWidths();
        assertThat(componentWidths.size()).isEqualTo(expression.getRequiredComponentWidthCount());
        assertThat(componentWidths.get(0)).isEqualTo(50.0);
        assertThat(componentWidths.get(1)).isEqualTo(150.0);
        assertThat(componentWidths.get(2)).isEqualTo(200.0);
        assertThat(componentWidths.get(3)).isEqualTo(250.0);
    }

    private static void testAugmentWithNSPrefixes(
            org.kie.workbench.common.dmn.api.definition.v1_1.Definitions definitions) {
        for (Namespace nsp : DMNModelInstrumentedBase.Namespace.values()) {
            definitions.getNsContext().put(nsp.getPrefix(), nsp.getUri());
        }
    }

    @SuppressWarnings("unchecked")
    private static Diagram<Graph, Metadata> newDiagramDecisionWithExpression(final Expression expression) {
        final Diagram<Graph, Metadata> diagram = new DiagramImpl("dmn graph", null);
        final Graph<DefinitionSet, Node> graph = mock(Graph.class);

        final Node<View, ?> diagramNode = mock(Node.class);
        final View diagramView = mock(View.class);
        final DMNDiagram dmnDiagram = new DMNDiagram();
        doReturn(diagramView).when(diagramNode).getContent();
        doReturn(dmnDiagram).when(diagramView).getDefinition();

        final Node<View, ?> decisionNode = mock(Node.class);
        final View decisionView = mock(View.class);
        final Decision decision = new Decision();
        doReturn(decisionView).when(decisionNode).getContent();
        doReturn(decision).when(decisionView).getDefinition();
        decision.setExpression(expression);

        doReturn(asList(diagramNode, decisionNode)).when(graph).nodes();
        ((DiagramImpl) diagram).setGraph(graph);

        return diagram;
    }

    private static void checkDecisionExpression(final Graph<?, Node<View, ?>> unmarshalledGraph,
            final Expression expression) {
        final Node<View, ?> decisionNode = nodeOfDefinition(unmarshalledGraph.nodes().iterator(), Decision.class);
        final Expression decisionNodeExpression = ((Decision) decisionNode.getContent().getDefinition())
                .getExpression();

        // The process of marshalling an Expression that has been programmatically instantiated (vs created in the UI)
        // leads to the _source_ Expression ComponentWidths being initialised. Therefore to ensure a like-for-like equality
        // comparison ensure the unmarshalled _target_ Expression has had it's ComponentWidths initialised too.
        decisionNodeExpression.getComponentWidths();
        ((Context) decisionNodeExpression).getContextEntry().get(0).getExpression().getComponentWidths();
        assertThat(decisionNodeExpression).isEqualTo(expression);
    }

    private static Node<View, ?> nodeOfDefinition(final Iterator<Node<View, ?>> nodesIterator, final Class aClass) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(nodesIterator, Spliterator.NONNULL), false)
                .filter(node -> aClass.isInstance(node.getContent().getDefinition())).findFirst().get();
    }

    @SafeVarargs
    private final XPath namespaceAwareXPath(Map.Entry<String, String>... pfxAndURI) {
        XPath result = XPathFactory.newInstance().newXPath();
        result.setNamespaceContext(new NamespaceContext() {
            final Map<String, String> pfxToURI = new HashMap<String, String>() {
                {
                    for (Map.Entry<String, String> pair : pfxAndURI) {
                        put(pair.getKey(), pair.getValue());
                    }
                }
            };
            final Map<String, String> URItoPfx = new HashMap<String, String>() {
                {
                    for (Map.Entry<String, String> pair : pfxAndURI) {
                        put(pair.getValue(), pair.getKey());
                    }
                }
            };

            @Override
            public String getNamespaceURI(String prefix) {
                return pfxToURI.get(prefix);
            }

            @Override
            public String getPrefix(String namespaceURI) {
                return URItoPfx.get(namespaceURI);
            }

            @Override
            public Iterator getPrefixes(String namespaceURI) {
                return pfxToURI.keySet().iterator();
            }
        });
        return result;
    }

    private DMNMarshaller getDMNMarshaller() {
        return new DMNMarshaller(new XMLEncoderDiagramMetadataMarshaller(), applicationFactoryManager,
                dmnMarshallerImportsHelper, getMarshaller());
    }

    private org.kie.dmn.api.marshalling.DMNMarshaller getMarshaller() {
        return new DMNMarshallerProducer().get();
    }

    private void assertXPathEquals(XPath xpathOriginal, XPath xpathRoundtrip, String xpathExpression,
            String expectedXml, String actualXml) throws XPathExpressionException {
        InputSource expected = new InputSource(new StringReader(expectedXml));
        InputSource actual = new InputSource(new StringReader(actualXml));
        assertEquals(xpathOriginal.compile(xpathExpression).evaluate(expected),
                xpathRoundtrip.compile(xpathExpression).evaluate(actual));
    }
}