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

com.swirlds.common.wiring.model.internal.analysis.WiringFlowchart Maven / Gradle / Ivy

Go to download

Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.

There is a newer version: 0.56.6
Show newest version
/*
 * Copyright (C) 2023-2024 Hedera Hashgraph, LLC
 *
 * 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 com.swirlds.common.wiring.model.internal.analysis;

import static com.swirlds.common.wiring.model.internal.analysis.ModelVertexMetaType.SCHEDULER;
import static com.swirlds.common.wiring.model.internal.analysis.ModelVertexMetaType.SUBSTITUTION;
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.CONCURRENT;
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.DIRECT;
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.DIRECT_THREADSAFE;
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.SEQUENTIAL;
import static com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType.SEQUENTIAL_THREAD;

import com.swirlds.common.wiring.model.diagram.ModelEdgeSubstitution;
import com.swirlds.common.wiring.model.diagram.ModelGroup;
import com.swirlds.common.wiring.model.diagram.ModelManualLink;
import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * A readable wiring flowchart.
 */
public class WiringFlowchart {

    public static final String SCHEDULER_COLOR = "135f12";
    public static final String DIRECT_SCHEDULER_COLOR = "12305f";
    public static final String TEXT_COLOR = "000";
    public static final String GROUP_COLOR = "9cf";
    public static final String SUBSTITUTION_COLOR = "5f1212";

    private final MermaidNameShortener nameProvider = new MermaidNameShortener();
    private final MermaidStyleManager styleManager = new MermaidStyleManager();

    /**
     * A map from vertex name to vertex.
     */
    private final Map vertexMap;

    /**
     * Draws a mermaid flowchart from the given wiring model.
     *
     * @param modelVertexMap a map from vertex name to vertex
     * @param substitutions  a list of edge substitutions to perform
     * @param groups         a list of groups to create
     * @param manualLinks    a list of manual links to draw
     */
    public WiringFlowchart(
            @NonNull final Map modelVertexMap,
            @NonNull final List substitutions,
            @NonNull final List groups,
            @NonNull final List manualLinks) {

        Objects.requireNonNull(modelVertexMap);

        vertexMap = copyVertexMap(modelVertexMap);
        addManualLinks(manualLinks);
        substituteEdges(substitutions);
        handleGroups(groups);
    }

    /**
     * Do a deep copy of the vertex map. Allows the local copy to be modified without affecting the original.
     *
     * @return a deep copy of the vertex map
     */
    @NonNull
    private Map copyVertexMap(@NonNull final Map original) {
        final Map copy = new HashMap<>();

        // First, copy the vertices without copying the edges.
        // We should only encounter StandardVertex instances here.
        for (final ModelVertex vertex : original.values()) {
            if (!(vertex instanceof StandardVertex)) {
                throw new IllegalStateException("Encountered a vertex that is not a StandardVertex");
            }
            final StandardVertex vertexCopy = new StandardVertex(
                    vertex.getName(),
                    vertex.getType(),
                    SCHEDULER,
                    vertex.getHyperlink(),
                    vertex.isInsertionIsBlocking());

            copy.put(vertex.getName(), vertexCopy);
        }

        // Next, copy the edges.
        for (final ModelVertex vertex : original.values()) {
            for (final ModelEdge edge : vertex.getOutgoingEdges()) {

                final ModelVertex source = copy.get(edge.getSource().getName());
                final ModelVertex destination = copy.get(edge.getDestination().getName());

                final ModelEdge edgeCopy =
                        new ModelEdge(source, destination, edge.getLabel(), edge.isInsertionIsBlocking(), false);

                source.getOutgoingEdges().add(edgeCopy);
            }
        }

        return copy;
    }

    /**
     * Add manual links to the flowchart.
     *
     * @param manualLinks the manual links to add
     */
    private void addManualLinks(@NonNull final List manualLinks) {
        for (final ModelManualLink link : manualLinks) {
            final ModelVertex source = vertexMap.get(link.source());
            final ModelVertex destination = vertexMap.get(link.target());

            if (source == null) {
                throw new IllegalStateException("Source vertex " + link.source() + " does not exist.");
            }

            if (destination == null) {
                throw new IllegalStateException("Destination vertex " + link.target() + " does not exist.");
            }

            final ModelEdge edge = new ModelEdge(source, destination, link.label(), false, true);
            source.getOutgoingEdges().add(edge);
        }
    }

    /**
     * Find all edges that need to be substituted and perform the substitution.
     */
    private void substituteEdges(@NonNull final List substitutions) {
        for (final ModelEdgeSubstitution substitution : substitutions) {
            substituteEdge(substitution);
        }
    }

    /**
     * Perform a single edge substitution.
     */
    private void substituteEdge(@NonNull final ModelEdgeSubstitution substitution) {
        // First, create a new vertex that will represent the destination of the substitution.
        final StandardVertex substitutedVertex =
                new StandardVertex(substitution.substitution(), DIRECT, SUBSTITUTION, null, true);
        vertexMap.put(substitution.substitution(), substitutedVertex);

        // Next, cause all substituted edges to point to this new vertex.
        for (final ModelVertex vertex : vertexMap.values()) {
            if (!substitution.source().equals(vertex.getName())) {
                // Only replace edges with the given source.
                continue;
            }

            final HashSet uniqueEdges = new HashSet<>();

            for (final ModelEdge edge : vertex.getOutgoingEdges()) {
                if (!substitution.edge().equals(edge.getLabel())) {
                    // Only replace destinations for edges with the given label.
                    uniqueEdges.add(edge);
                    continue;
                }

                edge.getDestination().getSubstitutedInputs().add(substitution.substitution());
                edge.setDestination(substitutedVertex);
                uniqueEdges.add(edge);
            }

            vertex.getOutgoingEdges().clear();
            vertex.getOutgoingEdges().addAll(uniqueEdges);
        }
    }

    /**
     * Handle groups in the order provided.
     */
    private void handleGroups(@NonNull final List groups) {
        for (final ModelGroup group : groups) {
            final GroupVertex groupVertex = createGroup(group);
            if (group.collapse()) {
                collapseGroup(groupVertex);
            }
        }
    }

    /**
     * Collect all vertices that are contained within the given group and create a new vertex that represents the
     * group.
     *
     * @param group the group to create a vertex for
     * @return the new vertex
     */
    private GroupVertex createGroup(@NonNull final ModelGroup group) {
        // Collect all vertices that are contained within the group.
        final List subVertices = new ArrayList<>();

        for (final String vertexName : group.elements()) {
            final ModelVertex subVertex = vertexMap.get(vertexName);
            if (subVertex == null) {
                throw new IllegalStateException("Vertex " + vertexName
                        + " is not in the vertex map. Can not insert into group " + group.name() + ".");
            }

            subVertices.add(subVertex);
        }

        // Remove those vertices from the vertex map.
        for (final ModelVertex subVertex : subVertices) {
            vertexMap.remove(subVertex.getName());
        }

        // Create a new vertex that represents the group.
        final GroupVertex groupVertex = new GroupVertex(group.name(), subVertices);
        vertexMap.put(group.name(), groupVertex);

        return groupVertex;
    }

    /**
     * Collapse a group of vertices into a single vertex.
     *
     * @param group the group to collapse
     */
    private void collapseGroup(@NonNull final GroupVertex group) {
        final List edges = collectEdges();
        final List groupVertices = collectGroupVertices(group);

        final Set hyperlinks = new HashSet<>();
        for (final ModelVertex vertex : groupVertices) {
            if (vertex.getHyperlink() != null) {
                hyperlinks.add(vertex.getHyperlink());
            }
        }
        final String hyperlink = hyperlinks.size() == 1 ? hyperlinks.iterator().next() : null;

        final TaskSchedulerType schedulerType = getSchedulerTypeOfCollapsedGroup(groupVertices);

        final StandardVertex newVertex = new StandardVertex(group.getName(), schedulerType, SCHEDULER, hyperlink, true);

        // Assign all vertices with a source that is collapsed to the new vertex.
        // Redirect all vertices with a destination that is collapsed to the new vertex.
        for (final ModelEdge edge : edges) {
            final boolean collapsedSource = groupVertices.contains(edge.getSource());
            final boolean collapsedDestination = groupVertices.contains(edge.getDestination());

            if (collapsedSource && collapsedDestination) {
                // If the source and or destination are collapsed, then the edge is removed.
                continue;
            }

            if (collapsedSource) {
                edge.setSource(newVertex);
                newVertex.getOutgoingEdges().add(edge);
            }

            if (collapsedDestination) {
                // Add and remove from set to avoid possible duplicates.
                edge.getSource().getOutgoingEdges().remove(edge);
                edge.setDestination(newVertex);
                edge.getSource().getOutgoingEdges().add(edge);
            }
        }

        // Extract substitutions from collapsed vertices.
        for (final ModelVertex vertex : groupVertices) {
            for (final String input : vertex.getSubstitutedInputs()) {
                newVertex.getSubstitutedInputs().add(input);
            }
        }

        // Remove old vertices from the vertex map.
        for (final ModelVertex vertex : groupVertices) {
            vertexMap.remove(vertex.getName());
        }

        // Finally, add the new vertex to the vertex map.
        vertexMap.put(newVertex.getName(), newVertex);
    }

    /**
     * When collapsing a group, determine the type of task scheduler type that should be displayed.
     */
    @NonNull
    private TaskSchedulerType getSchedulerTypeOfCollapsedGroup(@NonNull final List groupVertices) {

        boolean hasSequential = false;
        boolean hasState = false;

        for (final ModelVertex vertex : groupVertices) {
            if (vertex.getType() == CONCURRENT) {
                return CONCURRENT;
            }

            if (vertex.getType() == SEQUENTIAL || vertex.getType() == SEQUENTIAL_THREAD) {
                if (hasSequential) {
                    // We've detected more than one sequential scheduler type, so there is more than one logical
                    // thread of execution within this group.
                    return CONCURRENT;
                }
                hasSequential = true;
            }

            if (vertex.getType() == DIRECT) {
                hasState = true;
            }
        }

        if (hasSequential) {
            return SEQUENTIAL;
        } else {
            if (hasState) {
                return DIRECT;
            }
            return DIRECT_THREADSAFE;
        }
    }

    /**
     * Get all edges in the flowchart.
     *
     * @return all edges in the flowchart, sorted
     */
    private List collectGroupVertices(@NonNull final GroupVertex group) {
        final List vertices = new ArrayList<>();
        final LinkedList stack = new LinkedList<>();
        stack.addLast(group);

        while (!stack.isEmpty()) {
            final ModelVertex vertex = stack.removeLast();
            vertices.add(vertex);
            if (vertex instanceof final GroupVertex groupVertex) {
                for (final ModelVertex subVertex : groupVertex.getSubVertices()) {
                    stack.addLast(subVertex);
                }
            }
        }

        Collections.sort(vertices);
        return vertices;
    }

    /**
     * Get all edges in the flowchart.
     *
     * @return all edges in the flowchart, sorted
     */
    private List collectEdges() {
        final List edges = new ArrayList<>();
        final LinkedList stack = new LinkedList<>();

        for (final ModelVertex vertex : vertexMap.values()) {
            stack.addLast(vertex);
        }

        while (!stack.isEmpty()) {
            final ModelVertex vertex = stack.removeLast();
            edges.addAll(vertex.getOutgoingEdges());
            if (vertex instanceof final GroupVertex groupVertex) {
                for (final ModelVertex subVertex : groupVertex.getSubVertices()) {
                    stack.addLast(subVertex);
                }
            }
        }

        Collections.sort(edges);
        return edges;
    }

    /**
     * Render the flowchart to a string.
     *
     * @return the rendered flowchart
     */
    @NonNull
    public String render() {
        final StringBuilder sb = new StringBuilder();
        sb.append(
                """
                %%{
                    init: {
                        'flowchart': {'defaultRenderer': 'elk'},
                        'theme': 'base',
                        'themeVariables': {
                            'primaryColor': '#454545',
                            'primaryTextColor': '#EEEEEE',
                            'lineColor': '#C0C0C0'
                        }
                    }
                }%%
                flowchart TD
                """);

        final List sortedVertices =
                vertexMap.values().stream().sorted().toList();
        for (final ModelVertex vertex : sortedVertices) {
            vertex.render(sb, nameProvider, styleManager);
        }

        for (final ModelEdge edge : collectEdges()) {
            edge.render(sb, nameProvider);
        }

        styleManager.render(sb);

        return sb.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy