All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.automatiko.engine.codegen.process.image.SvgBpmnProcessImageGenerator Maven / Gradle / Ivy

The newest version!
package io.automatiko.engine.codegen.process.image;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.imageio.ImageIO;
import javax.swing.SwingConstants;

import org.apache.commons.collections.map.HashedMap;
import org.jfree.svg.SVGGraphics2D;
import org.jfree.svg.SVGHints;
import org.jfree.svg.ViewBox;
import org.jgrapht.ListenableGraph;
import org.jgrapht.ext.JGraphXAdapter;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DefaultListenableGraph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.model.mxICell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.handler.mxGraphHandler;

import io.automatiko.engine.api.definition.process.Connection;
import io.automatiko.engine.api.definition.process.Node;
import io.automatiko.engine.api.definition.process.NodeContainer;
import io.automatiko.engine.api.definition.process.WorkflowProcess;
import io.automatiko.engine.workflow.process.core.impl.NodeImpl;
import io.automatiko.engine.workflow.process.core.node.ActionNode;
import io.automatiko.engine.workflow.process.core.node.BoundaryEventNode;
import io.automatiko.engine.workflow.process.core.node.CompositeNode;
import io.automatiko.engine.workflow.process.core.node.CompositeNode.CompositeNodeEnd;
import io.automatiko.engine.workflow.process.core.node.EndNode;
import io.automatiko.engine.workflow.process.core.node.EventNode;
import io.automatiko.engine.workflow.process.core.node.EventSubProcessNode;
import io.automatiko.engine.workflow.process.core.node.FaultNode;
import io.automatiko.engine.workflow.process.core.node.ForEachNode;
import io.automatiko.engine.workflow.process.core.node.HumanTaskNode;
import io.automatiko.engine.workflow.process.core.node.Join;
import io.automatiko.engine.workflow.process.core.node.RuleSetNode;
import io.automatiko.engine.workflow.process.core.node.Split;
import io.automatiko.engine.workflow.process.core.node.StartNode;
import io.automatiko.engine.workflow.process.core.node.StateNode;
import io.automatiko.engine.workflow.process.core.node.SubProcessNode;
import io.automatiko.engine.workflow.process.core.node.TimerNode;
import io.automatiko.engine.workflow.process.core.node.WorkItemNode;
import io.automatiko.engine.workflow.process.executable.core.Metadata;

public class SvgBpmnProcessImageGenerator implements SvgProcessImageGenerator {

    private static final Logger LOGGER = LoggerFactory.getLogger(SvgBpmnProcessImageGenerator.class);

    @SuppressWarnings({ "unchecked", "serial" })
    private static Map, Integer> WIDTHS = new HashedMap() {
        {
            put(StartNode.class, 50);
            put(EndNode.class, 50);
            put(FaultNode.class, 50);
            put(Split.class, 50);
            put(Join.class, 50);
            put(EventNode.class, 50);
            put(TimerNode.class, 50);
            put(BoundaryEventNode.class, 50);
        }
    };

    @SuppressWarnings({ "unchecked", "serial" })
    private static Map, Integer> HEIGHTS = new HashedMap() {
        {
            put(StartNode.class, 50);
            put(EndNode.class, 50);
            put(FaultNode.class, 50);
            put(Split.class, 50);
            put(Join.class, 50);
            put(EventNode.class, 50);
            put(TimerNode.class, 50);
            put(BoundaryEventNode.class, 50);
        }
    };

    private WorkflowProcess workFlowProcess;

    private int maxWidth = 0;
    private int maxHeight = 0;

    private BasicStroke defaultStroke = new BasicStroke(2);
    private BasicStroke dotted = new BasicStroke(1.0f,
            BasicStroke.CAP_BUTT,
            BasicStroke.JOIN_MITER,
            10.0f, new float[] { 2.0f }, 0.0f);
    private BasicStroke dashed = new BasicStroke(2.0f,
            BasicStroke.CAP_BUTT,
            BasicStroke.JOIN_MITER,
            10.0f, new float[] { 2.0f }, 1.0f);

    public SvgBpmnProcessImageGenerator(WorkflowProcess workFlowProcess) {
        this.workFlowProcess = workFlowProcess;

    }

    @SuppressWarnings("unchecked")
    protected void createLayoutIfMissing() {
        Map> diagramInfo = (Map>) this.workFlowProcess.getMetaData()
                .get("DiagramInfo");
        if (diagramInfo != null) {

            ListenableGraph g = new DefaultListenableGraph<>(
                    new DefaultDirectedGraph<>(DefaultEdge.class));
            JGraphXAdapter jgxAdapter = new JGraphXAdapter<>(g);

            @SuppressWarnings("serial")
            mxGraphComponent component = new mxGraphComponent(jgxAdapter) {

                @Override
                protected mxGraphHandler createGraphHandler() {

                    return null;
                }

            };
            component.setConnectable(true);
            component.getGraph().setAllowDanglingEdges(false);

            Set allNodes = new LinkedHashSet<>(diagramInfo.keySet());
            for (List targets : diagramInfo.values()) {
                allNodes.addAll(targets);
            }

            // create vertexes
            for (String node : allNodes) {
                g.addVertex(node);
            }

            // create edges
            for (Entry> connection : diagramInfo.entrySet()) {

                for (String target : connection.getValue()) {
                    g.addEdge(connection.getKey(), target);
                }
            }
            Map> boundaryEvents = new HashMap<>();
            this.workFlowProcess.getNodesRecursively().stream().filter(n -> n instanceof BoundaryEventNode).forEach(bn -> {
                boundaryEvents.compute(((BoundaryEventNode) bn).getAttachedToNodeId(), (k, v) -> {
                    if (v == null) {
                        v = new ArrayList<>();
                    }
                    v.add((String) bn.getMetaData().get("UniqueId"));

                    return v;
                });

            });

            mxHierarchicalLayout layout = new mxHierarchicalLayout(jgxAdapter, SwingConstants.WEST);

            layout.setIntraCellSpacing(100);
            layout.execute(jgxAdapter.getDefaultParent());

            Map layedout = jgxAdapter.getVertexToCellMap();

            Map nodesById = this.workFlowProcess.getNodesRecursively().stream().flatMap(n -> {
                if (n instanceof ForEachNode) {
                    n = ((ForEachNode) n).getCompositeNode().getNodes()[0];
                }
                if (n instanceof CompositeNode) {
                    return Stream.of(((CompositeNode) n).getNodes());
                }
                return Stream.of(n);
            }).collect(Collectors.toMap(n -> (String) n.getMetaData().get("UniqueId"), node -> node, (k1, k2) -> k1));

            for (String node : sort(layedout.keySet())) {

                Node found = nodesById.get(node);
                if (found == null) {
                    continue;
                }
                found.getMetaData().put("width", WIDTHS.getOrDefault(extractNodeClass(found), 200));
                found.getMetaData().put("height", HEIGHTS.getOrDefault(extractNodeClass(found), 50));

                if (found instanceof BoundaryEventNode) {

                    mxICell attachedTo = layedout.get(((BoundaryEventNode) found).getAttachedToNodeId());

                    if (attachedTo != null) {
                        int x = Double.valueOf(attachedTo.getGeometry().getX()).intValue() + 20;

                        x = x + (Integer) found.getMetaData().get("width") / 2;

                        int y = Double.valueOf(attachedTo.getGeometry().getY()).intValue() + 20;
                        y = y + (Integer) found.getMetaData().get("height") - 10;

                        found.getMetaData().put("x", x);
                        found.getMetaData().put("y", y);
                    } else {
                        Node composite = processCompositeNode(((BoundaryEventNode) found).getAttachedToNodeId(), layedout);
                        if (composite != null) {
                            int x = x(composite);

                            x = x + (Integer) composite.getMetaData().get("width") / 6;

                            int y = y(composite);
                            y = y + (Integer) composite.getMetaData().get("height") - 10;

                            found.getMetaData().put("x", x);
                            found.getMetaData().put("y", y);

                            for (Connection conn : found.getOutgoingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE)) {

                                followAndCreateCoordinatesForConnection(conn, layedout, composite);
                            }
                            continue;
                        }
                    }
                } else {
                    found.getMetaData().put("x", Double.valueOf(layedout.get(node).getGeometry().getX()).intValue() + 20);
                    found.getMetaData().put("y", Double.valueOf(layedout.get(node).getGeometry().getY()).intValue() + 20);
                }

                for (Connection conn : found.getOutgoingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE)) {

                    createCoordinatesForConnection(conn, layedout, found);
                }

            }

            workFlowProcess.getNodesRecursively().stream()
                    .filter(n -> n instanceof CompositeNode && !(n instanceof EventSubProcessNode))
                    .forEach(n -> processCompositeNode(n, layedout));
        }
    }

    private void followAndCreateCoordinatesForConnection(Connection conn, Map layedout, Node found) {

        Node node = conn.getTo();

        node.getMetaData().put("x", x(node) + x(found));
        node.getMetaData().put("y", y(node) + y(found) + height(found));

        List xs = new ArrayList();

        xs.add((Integer) conn.getFrom().getMetaData().get("x") + (Integer) conn.getFrom().getMetaData().get("width"));
        xs.add(x(node));

        List ys = new ArrayList();

        ys.add((Integer) conn.getFrom().getMetaData().get("y") + ((Integer) conn.getFrom().getMetaData().get("height") / 2));
        ys.add(y(node) + (height(node) / 2));
        conn.getMetaData().put("x", xs);
        conn.getMetaData().put("y", ys);

        for (Connection conn2 : node.getOutgoingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE)) {

            followAndCreateCoordinatesForConnection(conn2, layedout, found);
        }
    }

    public List sort(Set ids) {
        List list = new ArrayList<>(ids);
        list.sort((id1, id2) -> {

            Node node1 = workFlowProcess.getNodesRecursively().stream()
                    .filter(n -> n.getMetaData().getOrDefault("UniqueId", "").equals(id1))
                    .findFirst().get();
            Node node2 = workFlowProcess.getNodesRecursively().stream()
                    .filter(n -> n.getMetaData().getOrDefault("UniqueId", "").equals(id2))
                    .findFirst().get();

            if (node1 instanceof BoundaryEventNode && node2 instanceof BoundaryEventNode) {
                return 0;
            } else if (node1 instanceof BoundaryEventNode && !(node2 instanceof BoundaryEventNode)) {
                return 1;
            } else if (!(node1 instanceof BoundaryEventNode) && node2 instanceof BoundaryEventNode) {
                return -1;
            } else {
                return 0;
            }

        });

        return list;
    }

    protected Node processCompositeNode(String id, Map layedout) {

        Node node = workFlowProcess.getNodesRecursively().stream()
                .filter(n -> n instanceof CompositeNode && n.getMetaData().getOrDefault("UniqueId", "").equals(id))
                .findFirst().orElse(null);

        if (node == null) {
            return null;
        }

        return processCompositeNode(node, layedout);
    }

    protected Node processCompositeNode(Node node, Map layedout) {
        if (node.getMetaData().containsKey("hidden")) {
            return null;
        }
        if (hasCoordinates(node)) {
            return node;
        }
        Node startNode = Stream.of(((CompositeNode) node).getNodes()).filter(n -> n instanceof StartNode)
                .findFirst().orElse(null);
        Node endNode = Stream.of(((CompositeNode) node).getNodes()).filter(n -> n instanceof EndNode).findFirst()
                .orElse(null);

        if (startNode == null || endNode == null) {
            return null;
        }

        int x = x(startNode);
        int y = y(startNode);

        int minY = Stream.of(((CompositeNode) node).getNodes()).mapToInt(n -> y(n)).min().getAsInt();
        int maxY = Stream.of(((CompositeNode) node).getNodes()).mapToInt(n -> y(n) + height(n)).min().getAsInt();

        int compositeX = x - 30;
        int compositeY = y - 50 - (maxY - minY) / 2;

        int width = x(endNode) + width(endNode) - compositeX + 30;

        node.getMetaData().put("x", compositeX);
        node.getMetaData().put("y", compositeY);
        node.getMetaData().put("width", width);
        node.getMetaData().put("height", maxY - minY + 150);

        for (Node n : ((CompositeNode) node).getNodes()) {
            n.getMetaData().put("x", x(n) - compositeX);
            n.getMetaData().put("y", y(n) - compositeY);

        }

        for (Connection conn : node.getOutgoingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE)) {
            createCoordinatesForConnection(conn, layedout, node);
        }

        List incoming = node.getIncomingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE).stream()
                .map(connection -> connection.getFrom()).collect(Collectors.toList());
        incoming.forEach(n -> {
            for (Connection conn : n.getOutgoingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE)) {
                List xs = new ArrayList();

                xs.add((Integer) n.getMetaData().get("x") + (Integer) n.getMetaData().get("width"));
                xs.add(x(node));

                List ys = new ArrayList();

                ys.add((Integer) n.getMetaData().get("y") + ((Integer) n.getMetaData().get("height") / 2));
                ys.add(y(node) + (height(node) / 2));
                conn.getMetaData().put("x", xs);
                conn.getMetaData().put("y", ys);

                if (ys.size() == 2 && ys.get(0) != ys.get(1)) {
                    if (node instanceof BoundaryEventNode) {
                        xs.add(1, xs.get(1) - 30);
                        ys.add(1, ys.get(0));
                    } else {

                        xs.add(1, xs.get(0) + 30);
                        ys.add(1, ys.get(1));
                    }
                }
            }
        });
        return node;
    }

    protected void createCoordinatesForConnection(Connection conn, Map layedout, Node node) {
        createCoordinatesForConnection(conn, layedout, node, 0, 0);
    }

    protected void createCoordinatesForConnection(Connection conn, Map layedout, Node node, int addToX,
            int addToY) {
        String toId = (String) conn.getTo().getMetaData().get("UniqueId");
        mxICell to = layedout.get(toId);

        if (to == null) {
            if (conn.getTo() instanceof CompositeNodeEnd) {
                toId = (String) ((Node) ((Node) conn.getTo().getParentContainer()).getParentContainer())
                        .getOutgoingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE).get(0).getTo()
                        .getMetaData().get("UniqueId");
                to = layedout.get(toId);
            } else if (toId.endsWith(":end")) { // special case when foreach with composite node is used then lookup node that actually is wrapped by for each
                toId = (String) (((Node) ((Node) ((Node) conn.getTo().getParentContainer()).getParentContainer())
                        .getParentContainer()))
                                .getOutgoingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE).get(0).getTo()
                                .getMetaData().get("UniqueId");
                to = layedout.get(toId);
            }
            if (to == null) {
                return;
            }
        }

        List xs = new ArrayList();

        xs.add((Integer) node.getMetaData().get("x") + (Integer) node.getMetaData().get("width"));
        xs.add(Double.valueOf(to.getGeometry().getX()).intValue() + 20);

        List ys = new ArrayList();

        ys.add((Integer) node.getMetaData().get("y") + ((Integer) node.getMetaData().get("height") / 2));
        ys.add(Double.valueOf(to.getGeometry().getY()).intValue() + 20
                + (HEIGHTS.getOrDefault(extractNodeClass(conn.getTo()), 50) / 2));

        if (ys.size() == 2 && ys.get(0) != ys.get(1)) {
            if (node instanceof BoundaryEventNode) {
                xs.add(1, xs.get(1) - 30);
                ys.add(1, ys.get(0));
            } else {

                xs.add(1, xs.get(0) + 30);
                ys.add(1, ys.get(1));
            }
        }

        if (addToX > 0) {
            xs = xs.stream().map(x -> x + addToX).collect(Collectors.toList());
        }
        if (addToY > 0) {
            ys = ys.stream().map(y -> y + addToY).collect(Collectors.toList());
        }
        conn.getMetaData().put("x", xs);
        conn.getMetaData().put("y", ys);
    }

    public Class extractNodeClass(Node node) {
        if (node instanceof ActionNode && "ProduceMessage".equals(((ActionNode) node).getMetaData(Metadata.TRIGGER_TYPE))) {
            return EventNode.class;
        }

        return node.getClass();
    }

    @Override
    public String generate() {
        try {
            createLayoutIfMissing();

            SVGGraphics2D g2 = new SVGGraphics2D(2000, 1000);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            g2.setFont(new Font(g2.getFont().getFontName(), Font.BOLD, g2.getFont().getSize()));
            g2.setStroke(defaultStroke);

            g2.drawString(workFlowProcess.getName(), 5, 10);

            buildNodeContainer(0, 0, workFlowProcess, g2);

            StringWriter writer = new StringWriter();
            writer.write(
                    "\n");
            writer.write(g2.getSVGElement(workFlowProcess.getId(), false, new ViewBox(0, 0, maxWidth + 20, maxHeight + 20),
                    null, null)
                    + "\n");

            return writer.toString();
        } catch (Throwable e) {
            LOGGER.warn("Unable to generate process image due to " + e.getMessage());
            LOGGER.info("Process image generation details", e);
            return null;
        }
    }

    /*
     * Build methods
     */

    protected void buildNodeContainer(int x, int y, NodeContainer nodeContainer, SVGGraphics2D g2) {
        try {
            for (Node node : nodeContainer.getNodes()) {

                if (node instanceof StartNode) {
                    buildStartEvent(x, y, (StartNode) node, g2);
                } else if (node instanceof EndNode) {
                    buildEndEvent(x, y, (EndNode) node, g2);
                } else if (node instanceof FaultNode) {
                    buildErrorEndEvent(x, y, (FaultNode) node, g2);
                } else if (node instanceof BoundaryEventNode) {
                    buildBoundaryEvent(x, y, node, g2);
                } else if (node instanceof EventNode || node instanceof StateNode) {
                    buildIntermediateEvent(x, y, node, g2);
                } else if (node instanceof HumanTaskNode) {
                    buildHumanTaskNode(x, y, (HumanTaskNode) node, g2);
                } else if (node instanceof ActionNode) {
                    buildScriptTaskNode(x, y, (ActionNode) node, g2);
                } else if (node instanceof WorkItemNode) {
                    buildServiceTaskNode(x, y, (WorkItemNode) node, g2);
                } else if (node instanceof Split || node instanceof Join) {
                    buildGateway(x, y, node, g2);
                } else if (node instanceof ForEachNode) {

                    buildNodeContainer(x(node), y(node), ((ForEachNode) node).getCompositeNode(), g2);
                } else if (node instanceof CompositeNode) {
                    if (hasCoordinates(node)) {
                        buildSubprocessNode(x, y, (CompositeNode) node, g2);
                        int sx = x(node);
                        int sy = y(node);

                        buildNodeContainer(sx, sy, (CompositeNode) node, g2);
                    } else {
                        buildNodeContainer(x, y, (CompositeNode) node, g2);
                    }
                } else if (node instanceof RuleSetNode) {
                    buildBusinessRuleTaskNode(x, y, (RuleSetNode) node, g2);
                } else if (node instanceof TimerNode) {
                    buildTimerEvent(x, y, (TimerNode) node, g2);
                } else if (node instanceof SubProcessNode) {
                    buildCallActivity(x, y, (SubProcessNode) node, g2);
                }

                buildSequenceFlow(x, y, node, g2);
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    protected void buildStartEvent(int x, int y, StartNode node, SVGGraphics2D g2) throws IOException {
        if (node.getMetaData().containsKey("hidden")) {
            return;
        }
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);
        Ellipse2D.Double start = new Ellipse2D.Double(x, y, width, height);

        g2.draw(start);
        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), start.getBounds(), g2.getFont(), (height / 2) + 10);

        if ("ConsumeMessage".equals(node.getMetaData("TriggerType"))) {
            drawCenteredIcon(g2, start.getBounds(), "MessageEventDefinition.png");
        } else if ("Signal".equals(node.getMetaData("TriggerType"))) {
            drawCenteredIcon(g2, start.getBounds(), "SignalEventDefinition.png");
        } else if ("Timer".equals(node.getMetaData("TriggerType"))) {
            drawCenteredIcon(g2, start.getBounds(), "TimerEventDefinition.png");
        } else if ("Error".equals(node.getMetaData("TriggerType"))) {
            drawCenteredIcon(g2, start.getBounds(), "ErrorEventDefinition.png");
        } else if ("Escalation".equals(node.getMetaData("TriggerType"))) {
            drawCenteredIcon(g2, start.getBounds(), "EscalationEventDefinition.png");
        } else if ("Condition".equals(node.getMetaData("TriggerType"))) {
            drawCenteredIcon(g2, start.getBounds(), "ConditionalEventDefinition.png");
        }
    }

    protected void buildEndEvent(int x, int y, EndNode node, SVGGraphics2D g2) throws IOException {
        if (node.getMetaData().containsKey("hidden")) {
            return;
        }
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);
        Ellipse2D.Double end = new Ellipse2D.Double(x, y, width, height);

        g2.setStroke(new BasicStroke(4));
        g2.draw(end);
        if (node.isTerminate()) {
            Ellipse2D.Double innerend = new Ellipse2D.Double(x + 8, y + 8, width - 15, height - 15);
            g2.draw(innerend);
            g2.fill(innerend);
        } else if ("ProduceMessage".equals(node.getMetaData("TriggerType"))) {
            drawCenteredIcon(g2, end.getBounds(), "ThrowMessageEventDefinition.png");
        } else if ("signal".equals(node.getMetaData("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "ThrowSignalEventDefinition.png");
        } else if ("error".equals(node.getMetaData("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "ThrowErrorEventDefinition.png");
        } else if ("escalation".equals(node.getMetaData("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "ThrowEscalationEventDefinition.png");
        } else if ("Compensation".equals(node.getMetaData().get("TriggerType"))) {
            drawCenteredIcon(g2, end.getBounds(), "CompensateEventDefinition.png");
        }

        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), end.getBounds(), g2.getFont(), (height(node) / 2) + 10);
        g2.setStroke(defaultStroke);
    }

    protected void buildErrorEndEvent(int x, int y, FaultNode node, SVGGraphics2D g2) throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);
        Ellipse2D.Double end = new Ellipse2D.Double(x, y, width, height);

        g2.setStroke(new BasicStroke(4));
        g2.draw(end);
        drawCenteredIcon(g2, end.getBounds(), "ThrowErrorEventDefinition.png");

        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), end.getBounds(), g2.getFont(), (height(node) / 2) + 10);
        g2.setStroke(defaultStroke);
    }

    protected void buildIntermediateEvent(int x, int y, Node node, SVGGraphics2D g2) throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);
        Ellipse2D.Double end = new Ellipse2D.Double(x, y, width, height);
        g2.draw(end);

        Ellipse2D.Double innerend = new Ellipse2D.Double(x + 5, y + 5, width - 10, height - 10);
        g2.draw(innerend);

        if ("message".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "MessageEventDefinition.png");
        } else if ("signal".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "SignalEventDefinition.png");
        } else if ("timer".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "TimerEventDefinition.png");
        } else if ("error".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "ErrorEventDefinition.png");
        } else if ("escalation".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "EscalationEventDefinition.png");
        } else if ("condition".equals(node.getMetaData().get("EventType")) || node instanceof StateNode) {
            drawCenteredIcon(g2, end.getBounds(), "ConditionalEventDefinition.png");
        } else if ("compensation".equals(node.getMetaData().get("EventType")) || node instanceof StateNode) {
            drawCenteredIcon(g2, end.getBounds(), "CompensateEventDefinition.png");
        }

        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), end.getBounds(), g2.getFont(), (height(node) / 2) + 10);
        g2.setStroke(defaultStroke);
    }

    protected void buildBoundaryEvent(int x, int y, Node node, SVGGraphics2D g2) throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);

        if (Boolean.FALSE.equals(node.getMetaData().get("CancelActivity"))) {
            g2.setStroke(dashed);
        }

        Ellipse2D.Double end = new Ellipse2D.Double(x, y, width, height);
        g2.draw(end);
        g2.setColor(new Color(255, 255, 255));

        Ellipse2D.Double innerend = new Ellipse2D.Double(x + 5, y + 5, width - 10, height - 10);
        g2.setColor(new Color(0, 0, 0));
        g2.draw(innerend);

        if ("message".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "MessageEventDefinition.png");
        } else if ("signal".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "SignalEventDefinition.png");
        } else if ("timer".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "TimerEventDefinition.png");
        } else if ("error".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "ErrorEventDefinition.png");
        } else if ("escalation".equals(node.getMetaData().get("EventType"))) {
            drawCenteredIcon(g2, end.getBounds(), "EscalationEventDefinition.png");
        } else if ("condition".equals(node.getMetaData().get("EventType")) || node instanceof StateNode) {
            drawCenteredIcon(g2, end.getBounds(), "ConditionalEventDefinition.png");
        } else if ("compensation".equals(node.getMetaData().get("EventType")) || node instanceof StateNode) {
            drawCenteredIcon(g2, end.getBounds(), "CompensateEventDefinition.png");
        }

        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), end.getBounds(), g2.getFont(), (height(node) / 2) + 10);
        g2.setStroke(defaultStroke);
    }

    protected void buildTimerEvent(int x, int y, TimerNode node, SVGGraphics2D g2) throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);
        Ellipse2D.Double end = new Ellipse2D.Double(x, y, width, height);
        g2.draw(end);

        Ellipse2D.Double innerend = new Ellipse2D.Double(x + 5, y + 5, width - 10, height - 10);
        g2.draw(innerend);
        drawCenteredIcon(g2, end.getBounds(), "TimerEventDefinition.png");

        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), end.getBounds(), g2.getFont(), (height(node) / 2) + 10);
        g2.setStroke(defaultStroke);
    }

    protected void buildCallActivity(int x, int y, SubProcessNode node, SVGGraphics2D g2)
            throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);

        g2.setStroke(new BasicStroke(5));
        RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(x, y, width, height, 10, 10);
        g2.setPaint(new Color(0, 0, 0));
        g2.draw(roundedRectangle);
        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), roundedRectangle.getBounds(), g2.getFont(), 0);

        Rectangle rect = roundedRectangle.getBounds();
        BufferedImage image = ImageIO.read(getClass().getResource("/icons/CallActivityPlus.png"));
        int ix = (int) (rect.getX() + (rect.getWidth() - image.getWidth()) / 2);
        int iy = (int) (rect.getY() + ((rect.getHeight() - image.getHeight()) - 3));

        setImageNodeId(node, g2);
        g2.drawImage(image, ix, iy, null);

        g2.setStroke(defaultStroke);

        drawWarningPlaceholderIcon(node, g2, roundedRectangle.getBounds());
    }

    protected void buildHumanTaskNode(int x, int y, HumanTaskNode node, SVGGraphics2D g2)
            throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);

        RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(x, y, width, height, 10, 10);
        g2.setPaint(new Color(0, 0, 0));
        g2.draw(roundedRectangle);
        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), roundedRectangle.getBounds(), g2.getFont(), 0);

        g2.drawImage(ImageIO.read(getClass().getResource("/icons/UserTask.png")), x + 2, y + 2, null);

        drawWarningPlaceholderIcon(node, g2, roundedRectangle.getBounds());
    }

    protected void buildScriptTaskNode(int x, int y, ActionNode node, SVGGraphics2D g2)
            throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);

        if (node.getMetaData("TriggerType") != null) {
            Ellipse2D.Double end = new Ellipse2D.Double(x, y, width, height);
            g2.draw(end);

            Ellipse2D.Double innerend = new Ellipse2D.Double(x + 5, y + 5, width - 10, height - 10);
            g2.draw(innerend);

            if ("ProduceMessage".equals(node.getMetaData("TriggerType"))) {
                drawCenteredIcon(g2, end.getBounds(), "ThrowMessageEventDefinition.png");
            } else if ("signal".equals(node.getMetaData("EventType"))) {
                drawCenteredIcon(g2, end.getBounds(), "ThrowSignalEventDefinition.png");
            } else if ("error".equals(node.getMetaData("EventType"))) {
                drawCenteredIcon(g2, end.getBounds(), "ThrowErrorEventDefinition.png");
            } else if ("escalation".equals(node.getMetaData("EventType"))) {
                drawCenteredIcon(g2, end.getBounds(), "ThrowEscalationEventDefinition.png");
            }

            setTextNodeId(node, g2);
            drawCenteredString(g2, node.getName(), end.getBounds(), g2.getFont(), (height(node) / 2) + 10);
            g2.setStroke(defaultStroke);
        } else {

            RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(x, y, width, height, 10, 10);
            g2.setPaint(new Color(0, 0, 0));
            g2.draw(roundedRectangle);
            setTextNodeId(node, g2);
            drawCenteredString(g2, node.getName(), roundedRectangle.getBounds(), g2.getFont(), 0);

            g2.drawImage(ImageIO.read(getClass().getResource("/icons/ScriptTask.png")), x + 2, y + 2, null);

            drawWarningPlaceholderIcon(node, g2, roundedRectangle.getBounds());
        }
    }

    protected void buildServiceTaskNode(int x, int y, WorkItemNode node, SVGGraphics2D g2)
            throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);
        RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(x, y, width, height, 10, 10);
        g2.setPaint(new Color(0, 0, 0));
        g2.draw(roundedRectangle);
        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), roundedRectangle.getBounds(), g2.getFont(), 0);

        g2.drawImage(ImageIO.read(getClass().getResource("/icons/ServiceTask.png")), x + 2, y + 2, null);

        drawWarningPlaceholderIcon(node, g2, roundedRectangle.getBounds());
    }

    protected void buildBusinessRuleTaskNode(int x, int y, RuleSetNode node, SVGGraphics2D g2)
            throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);
        RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(x, y, width, height, 10, 10);
        g2.setPaint(new Color(0, 0, 0));
        g2.draw(roundedRectangle);
        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), roundedRectangle.getBounds(), g2.getFont(), 0);

        g2.drawImage(ImageIO.read(getClass().getResource("/icons/BusinessRuleTask.png")), x + 2, y + 2, null);

        drawWarningPlaceholderIcon(node, g2, roundedRectangle.getBounds());
    }

    protected void buildGateway(int x, int y, Node node, SVGGraphics2D g2) throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);
        Polygon p = new Polygon();

        p.addPoint(x, y + (height / 2));
        p.addPoint(x + (width / 2), y);
        p.addPoint(x + width, y + (height / 2));
        p.addPoint(x + (width / 2), y + height);

        g2.drawPolygon(p);
        setTextNodeId(node, g2);
        drawCenteredString(g2, node.getName(), p.getBounds(), g2.getFont(), (height(node) / 2) + 10);

        Font current = g2.getFont();
        g2.setFont(new Font("Montserrat", Font.BOLD, 25));

        String gatewayMarker = "";

        if (node instanceof Split) {
            switch (((Split) node).getType()) {
                case Split.TYPE_XOR:
                    gatewayMarker = "X";
                    break;
                case Split.TYPE_AND:
                    gatewayMarker = "+";
                    break;

                case Split.TYPE_OR:
                    gatewayMarker = "o";
                    break;
                default:
                    break;
            }
        } else if (node instanceof Join) {
            switch (((Join) node).getType()) {
                case Join.TYPE_XOR:
                    gatewayMarker = "X";
                    break;
                case Join.TYPE_AND:
                    gatewayMarker = "+";
                    break;

                case Join.TYPE_OR:
                    gatewayMarker = "o";
                    break;
                default:
                    break;
            }
        }

        drawCenteredString(g2, gatewayMarker, p.getBounds(), g2.getFont(), 0);
        g2.setFont(current);

    }

    protected void buildSubprocessNode(int x, int y, CompositeNode node, SVGGraphics2D g2)
            throws IOException {
        setNodeId(node, g2);
        x += x(node);
        y += y(node);
        int width = width(node);
        int height = height(node);

        if (node instanceof EventSubProcessNode) {
            g2.setStroke(dotted);
        }

        RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(x, y, width, height, 10, 10);
        g2.setPaint(new Color(0, 0, 0));
        g2.draw(roundedRectangle);
        setTextNodeId(node, g2);
        g2.drawString(node.getName(), x + 10, y + 10);
        g2.setStroke(defaultStroke);

        drawWarningPlaceholderIcon(node, g2, roundedRectangle.getBounds());
    }

    @SuppressWarnings("unchecked")
    protected void buildSequenceFlow(int x, int y, Node node, SVGGraphics2D g2) {
        g2.setPaint(new Color(0, 0, 0));
        List outgoing = node
                .getOutgoingConnections(io.automatiko.engine.workflow.process.core.Node.CONNECTION_DEFAULT_TYPE);

        if (outgoing != null && !outgoing.isEmpty()) {

            for (Connection connection : outgoing) {

                if (connection.getMetaData().get("x") != null && connection.getMetaData().get("y") != null) {
                    int[] linestart = ((List) connection.getMetaData().get("x")).stream().mapToInt(Integer::intValue)
                            .toArray();

                    int[] lineend = ((List) connection.getMetaData().get("y")).stream().mapToInt(Integer::intValue)
                            .toArray();
                    if (connection.getMetaData().get("association") != null) {
                        g2.setStroke(dashed);
                    }
                    g2.drawPolyline(linestart, lineend, linestart.length);
                    drawArrowLine(g2, linestart[0], lineend[0], linestart[linestart.length - 1], lineend[lineend.length - 1], 5,
                            5);
                    g2.setStroke(defaultStroke);
                }
            }
        }
    }

    /*
     * Helper methods
     */

    protected boolean hasCoordinates(Node node) {
        if (node.getMetaData().containsKey("x") || node.getMetaData().containsKey("y")) {
            return true;
        }

        return false;
    }

    protected int x(Node node) {
        if (node instanceof ForEachNode) {
            return 0;
        }
        return (int) node.getMetaData().get("x");
    }

    protected int y(Node node) {
        if (node instanceof ForEachNode) {
            return 0;
        }
        return (int) node.getMetaData().get("y");
    }

    protected int width(Node node) {
        int width = (int) node.getMetaData().get("x") + (int) node.getMetaData().get("width");
        if (width > maxWidth) {
            maxWidth = width;
        }
        return (int) node.getMetaData().get("width");
    }

    protected int height(Node node) {
        int height = (int) node.getMetaData().get("y") + (int) node.getMetaData().get("height");
        if (height > maxHeight) {
            maxHeight = height;
        }
        return (int) node.getMetaData().get("height");
    }

    /**
     * Draw an arrow line between two points.
     * 
     * @param g the graphics component.
     * @param x1 x-position of first point.
     * @param y1 y-position of first point.
     * @param x2 x-position of second point.
     * @param y2 y-position of second point.
     * @param d the width of the arrow.
     * @param h the height of the arrow.
     */
    private void drawArrowLine(Graphics g, int x1, int y1, int x2, int y2, int d, int h) {
        int dx = x2 - x1, dy = y2 - y1;
        double D = Math.sqrt(dx * dx + dy * dy);
        double xm = D - d, xn = xm, ym = h, yn = -h, x;
        double sin = dy / D, cos = dx / D;

        x = xm * cos - ym * sin + x1;
        ym = xm * sin + ym * cos + y1;
        xm = x;

        x = xn * cos - yn * sin + x1;
        yn = xn * sin + yn * cos + y1;
        xn = x;

        int[] xpoints = { x2, (int) xm, (int) xn };
        int[] ypoints = { y2, (int) ym, (int) yn };

        g.fillPolygon(xpoints, ypoints, 3);
    }

    public void drawCenteredString(Graphics g, String text, Rectangle rect, Font font, int extraY) {
        if (text == null) {
            return;
        }
        // Get the FontMetrics
        FontMetrics metrics = g.getFontMetrics(font);
        int pad = 0;
        int textLength = metrics.stringWidth(text);
        int maxTextWidth = (int) (rect.getWidth() - 20);
        if (extraY == 0 && textLength > maxTextWidth) {
            double singleCharLength = textLength / text.length();
            int max = (int) (maxTextWidth / singleCharLength);
            if (text.length() > max) {
                text = text.substring(0, max) + "...";
                pad = 5;
            }
        }

        // Determine the X coordinate for the text
        int x = (int) (rect.getX() + (rect.getWidth() - metrics.stringWidth(text)) / 2);
        // Determine the Y coordinate for the text (note we add the ascent, as in java 2d 0 is top of the screen)
        int y = (int) (rect.getY() + ((rect.getHeight() - metrics.getHeight()) / 2) + metrics.getAscent());
        // Set the font
        g.setFont(font);
        // Draw the String
        g.drawString(text, x + pad, y + extraY);
    }

    public void drawCenteredIcon(Graphics g2, Rectangle rect, String icon) throws IOException {
        BufferedImage image = ImageIO.read(getClass().getResource("/icons/" + icon));
        // Determine the X coordinate for the text
        int ix = (int) (rect.getX() + (rect.getWidth() - image.getWidth()) / 2);
        // Determine the Y coordinate for the text (note we add the ascent, as in java 2d 0 is top of the screen)
        int iy = (int) (rect.getY() + ((rect.getHeight() - image.getHeight()) / 2));

        g2.drawImage(image, ix, iy, null);
    }

    public void drawWarningPlaceholderIcon(Node node, SVGGraphics2D g2, Rectangle rect) throws IOException {
        BufferedImage warnImage = ImageIO.read(getClass().getResource("/icons/empty.png"));
        int wix = (int) (rect.getX() + rect.getWidth() - 23);
        int wiy = (int) (rect.getY() + 3);
        setWarningImageNodeId(node, g2);
        g2.drawImage(warnImage, wix, wiy, 20, 20, null);
    }

    protected void setTextNodeId(Node node, SVGGraphics2D g2) {
        g2.setRenderingHint(SVGHints.KEY_ELEMENT_ID, node.getMetaData().get("UniqueId") + "_text");
    }

    protected void setImageNodeId(Node node, SVGGraphics2D g2) {
        g2.setRenderingHint(SVGHints.KEY_ELEMENT_ID, node.getMetaData().get("UniqueId") + "_image");
    }

    protected void setWarningImageNodeId(Node node, SVGGraphics2D g2) {
        g2.setRenderingHint(SVGHints.KEY_ELEMENT_ID, node.getMetaData().get("UniqueId") + "_warn_image");
    }

    protected void setNodeId(Node node, SVGGraphics2D g2) {
        g2.setRenderingHint(SVGHints.KEY_ELEMENT_ID, node.getMetaData().get("UniqueId"));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy