diff --git a/AlgebraicDataflowArchitectureModel/src/application/editor/stages/ControlFlowModelingStage.java b/AlgebraicDataflowArchitectureModel/src/application/editor/stages/ControlFlowModelingStage.java index ed2e6ef..82376c9 100644 --- a/AlgebraicDataflowArchitectureModel/src/application/editor/stages/ControlFlowModelingStage.java +++ b/AlgebraicDataflowArchitectureModel/src/application/editor/stages/ControlFlowModelingStage.java @@ -576,8 +576,19 @@ mxCell root = (mxCell) graph.getDefaultParent(); mxCell layerCell = (mxCell) root.getChildAt(layer); final Map resourceNodeCells = new HashMap<>(); - + + CallGraph callGraph = null; + if (layer == PUSH_FLOW_LAYER) { + callGraph = controlFlowGraph.getPushCallGraph(); + } else if (layer == PULL_FLOW_LAYER) { + callGraph = controlFlowGraph.getPullCallGraph(); + } + for (ResourceNode rootNode : controlFlowGraph.getDataFlowGraph().getRootResourceNodes()) { + if (callGraph != null && !isResourceOrDescendantsInCallGraph(rootNode, callGraph)) { + continue; + } + ResourcePath rootResourcePath = rootNode.getPrimaryResourcePath(); int width = 320; int height = 160; @@ -585,7 +596,7 @@ mxCell insertedCell = (mxCell) graph.insertVertex(layerCell, null, rootResourcePath.getLeafResourceName(), 20, 20, width, height, "shape=ellipse;perimeter=ellipsePerimeter;verticalAlign=top"); // insert a resource as a vertex resourceNodeCells.put(rootNode, insertedCell); - createChildResourceNodeCell(graph, insertedCell, rootNode, resourceNodeCells, width / 2, height / 2); + createChildResourceNodeCell(graph, insertedCell, rootNode, resourceNodeCells, width / 2, height / 2, callGraph); } return resourceNodeCells; } @@ -597,9 +608,14 @@ * @param parentCell Parent cell of the new cell. * @param parentNode {@link ResourceNode} instance to be registered. * @param resourceNodeCells The map of {@link ResourceNode} and its corresponding cell. + * @param callGraph The call graph to check for node existence. */ - private void createChildResourceNodeCell(final mxGraph graph, final mxCell parentCell, final ResourceNode parentNode, final Map resourceNodeCells, int width, int height) { + private void createChildResourceNodeCell(final mxGraph graph, final mxCell parentCell, final ResourceNode parentNode, final Map resourceNodeCells, int width, int height, final CallGraph callGraph) { for (ResourceNode childNode : parentNode.getChildren()) { + if (callGraph != null && !isResourceOrDescendantsInCallGraph(childNode, callGraph)) { + continue; + } + ResourcePath resourcePath = childNode.getPrimaryResourcePath(); mxGeometry parentGeo = parentCell.getGeometry(); @@ -613,9 +629,24 @@ mxCell insertedCell = (mxCell) graph.insertVertex(parentCell, null, resourcePath.getName(), x, y, width, height, "shape=ellipse;perimeter=ellipsePerimeter;verticalAlign=top"); // insert a resource as a vertex resourceNodeCells.put(childNode, insertedCell); - createChildResourceNodeCell(graph, insertedCell, childNode, resourceNodeCells, width / 2, height / 2); + createChildResourceNodeCell(graph, insertedCell, childNode, resourceNodeCells, width / 2, height / 2, callGraph); } } + + /** + * Check if the resource node or any of its descendants are in the call graph. + * + * @param node The resource node to check. + * @param callGraph The call graph. + * @return true if the node or any descendant is in the call graph. + */ + private boolean isResourceOrDescendantsInCallGraph(ResourceNode node, CallGraph callGraph) { + if (callGraph.getStatefulObjectNode(node) != null) return true; + for (ResourceNode child : node.getChildren()) { + if (isResourceOrDescendantsInCallGraph(child, callGraph)) return true; + } + return false; + } /** * Create and register a new graph cell for event channel nodes. diff --git a/AlgebraicDataflowArchitectureModel/src/models/controlFlowModel/ControlFlowGraph.java b/AlgebraicDataflowArchitectureModel/src/models/controlFlowModel/ControlFlowGraph.java index 2999ecb..d0c991d 100644 --- a/AlgebraicDataflowArchitectureModel/src/models/controlFlowModel/ControlFlowGraph.java +++ b/AlgebraicDataflowArchitectureModel/src/models/controlFlowModel/ControlFlowGraph.java @@ -8,6 +8,7 @@ import models.algebra.Variable; import models.dataConstraintModel.Channel; import models.dataConstraintModel.ChannelMember; +import models.dataConstraintModel.ResourcePath; import models.dataFlowModel.*; import java.util.HashMap; @@ -33,7 +34,7 @@ for (Edge resToCh : dataFlowGraph.getEdges()) { if (!((DataFlowEdge) resToCh).isChannelToResource()) { // A resource to channel edge - PushPullAttribute pushPull = ((PushPullAttribute) ((DataFlowEdge) resToCh).getAttribute()); + PushPullAttribute pushPull = ((PushPullAttribute) resToCh.getAttribute()); ResourceNode srcResNode = (ResourceNode) resToCh.getSource(); ChannelNode chNode = (ChannelNode) resToCh.getDestination(); for (Edge chToRes : chNode.getOutEdges()) { @@ -52,30 +53,185 @@ DataTransferChannel evCh = (DataTransferChannel) ch; traverseChannelHierarchy(evCh, pushCallGraph); } + + setupReferenceEdges(dataFlowGraph, model); } - - private void traverseChannelHierarchy(DataTransferChannel evCh, CallGraph pushCallGraph) { - EventChannelObjectNode srcNode = new EventChannelObjectNode(evCh); - for (ChannelMember cm : evCh.getChannelMembers()) { - if (srcNode.getName() == null) { - Expression exp = cm.getStateTransition().getMessageExpression(); - if (exp instanceof Term) { - srcNode.setName(((Term) exp).getSymbol().getName()); - } else if (exp instanceof Variable) { - srcNode.setName(((Variable) exp).getName()); + + /** + * Establishes reference edges in the push and pull call graphs using the given data flow graph + * and data transfer model. This method processes each channel node in the data flow graph, + * determining whether it operates in a push or pull context. If applicable, it collects + * the relevant input and output resource nodes and adds edges between them in + * their respective call graphs. + * + * @param dataFlowGraph The data flow graph containing channel nodes and resource nodes. + * It represents the overall structure and relationships between + * channels and resources in the system. + * @param model The data transfer model supporting context evaluation (push or pull) + * and providing additional relevant data required for graph construction. + */ + private void setupReferenceEdges(DataFlowGraph dataFlowGraph, DataTransferModel model) { + for (ChannelNode channelNode : dataFlowGraph.getChannelNodes()) { + DataTransferChannel channel = channelNode.getChannel(); + boolean isPush = isPushContext(channelNode, model); + boolean isPull = isPullContext(channelNode, model); + + if (isPush || isPull) { + for (ResourcePath targetResourcePath : channel.getReferenceResources()) { + for (ResourceNode inputResourceNode : dataFlowGraph.getResourceNodes(targetResourcePath.getResourceHierarchy())) { + Set outputResourceNodes = new HashSet<>(); + collectOutputResources(channelNode, outputResourceNodes); + + for (ResourceNode destinationNode : outputResourceNodes) { + if (isPush) { + pushCallGraph.addEdge(inputResourceNode, destinationNode, PushPullValue.PUSH); + } + if (isPull) { + pullCallGraph.addEdge(destinationNode, inputResourceNode, PushPullValue.PULL); + } + } + } } } - for (ResourceNode dstResNode : dataFlowGraph.getResourceNodes(cm.getResource().getResourceHierarchy())) { - StatefulObjectNode dstNode = pushCallGraph.getStatefulObjectNode(dstResNode); - if (dstNode == null) { - pushCallGraph.addNode(dstResNode); - dstNode = pushCallGraph.getStatefulObjectNode(dstResNode); - } - // from an input event channel to a resource - pushCallGraph.insertEdge(srcNode, dstNode, PushPullValue.PUSH, 0); - } } - for (Channel child : evCh.getChildren()) { + } + + /** + * Determines whether the given channel node operates in a push-based data transfer context. + * This method evaluates the channel node's input channels and incoming edges to check + * if they involve a "PUSH" operation. If no push context is identified, the method + * recursively checks the parent channel node until a push context is found or the + * hierarchy is fully traversed. + * + * @param channelNode The channel node for which the push-based data transfer context + * is being determined. This node represents the entry point + * for the evaluation. + * @param model The data transfer model containing channel and flow information + * necessary for evaluating the data transfer context. + * @return {@code true} if the channel node (or any of its parent nodes) operates + * in a push-based data transfer context; {@code false} otherwise. + */ + private boolean isPushContext(ChannelNode channelNode, DataTransferModel model) { + if (model.getInputChannels().contains(channelNode.getChannel())) { + return true; + } + for (Edge resourceToChannel : channelNode.getInEdges()) { + if (resourceToChannel instanceof DataFlowEdge) { + PushPullAttribute attribute = (PushPullAttribute) (resourceToChannel).getAttribute(); + if (attribute != null && attribute.getOptions().contains(PushPullValue.PUSH)) { + return true; + } + } + } + if (channelNode.getParent() != null) { + return isPushContext(channelNode.getParent(), model); + } + return false; + } + + /** + * Determines whether the given channel node operates in a pull-based data transfer context. + * The method evaluates all incoming edges of the specified channel node to check if any + * data flow involves a "PULL" operation. If none is found, it recursively checks the parent + * channel node until a pull context is identified or the hierarchy is exhausted. + * + * @param channelNode The channel node whose data transfer context is being evaluated. + * This node serves as the entry point for the context determination. + * @param model The data transfer model that may provide additional contextual + * information used during the evaluation. + * @return {@code true} if the channel node (or any of its parent nodes) is in a pull-based + * data transfer context; {@code false} otherwise. + */ + private boolean isPullContext(ChannelNode channelNode, DataTransferModel model) { + for (Edge resourceToChannel : channelNode.getInEdges()) { + if (resourceToChannel instanceof DataFlowEdge) { + PushPullAttribute attribute = (PushPullAttribute) (resourceToChannel).getAttribute(); + if (attribute != null && attribute.getOptions().contains(PushPullValue.PULL)) { + return true; + } + } + } + if (channelNode.getParent() != null) { + return isPullContext(channelNode.getParent(), model); + } + return false; + } + + /** + * Collects all output resources associated with the given channel node and + * accumulates them into the provided result set. The method identifies the root + * of the channel hierarchy by traversing upwards through the parent nodes, + * then recursively processes the hierarchy starting from the root to collect + * associated resource nodes. + * + * @param channelNode The starting channel node from which the root channel node + * is determined. The resource collection begins from the + * root node of the hierarchy. + * @param results A set where the collected resource nodes are stored. + * This set is updated during the execution of the method. + */ + private void collectOutputResources(ChannelNode channelNode, Set results) { + // Find root + ChannelNode root = channelNode; + while (root.getParent() != null) { + root = root.getParent(); + } + collectOutputResourcesRecursive(root, results); + } + + /** + * Recursively collects the output resources connected to the given channel node + * and adds them to the provided result set. This method traverses the outgoing + * edges of the specified channel node to identify associated resource nodes. + * It then recursively processes all child nodes of the provided channel node. + * + * @param channelNode The root channel node whose output resources are to be collected. + * It serves as the starting point for the recursive traversal. + * @param results A set where the identified output resource nodes are accumulated. + * This set is updated during the traversal and eventually contains + * all output resources reachable from the root channel node. + */ + private void collectOutputResourcesRecursive(ChannelNode channelNode, Set results) { + for (Edge outputEdge : channelNode.getOutEdges()) { + results.add((ResourceNode) outputEdge.getDestination()); + } + for (ChannelNode child : channelNode.getChildren()) { + collectOutputResourcesRecursive(child, results); + } + } + + /** + * Traverses the hierarchy of a {@link DataTransferChannel} and constructs connections + * in the provided push call graph between the event channels and associated + * stateful objects. + * + * @param channel The root DataTransferChannel to traverse, representing + * the starting point of the channel hierarchy. + * @param pushCallGraph The CallGraph to which push connections between + * channels and resources are added as the traversal progresses. + */ + private void traverseChannelHierarchy(DataTransferChannel channel, CallGraph pushCallGraph) { + EventChannelObjectNode parentChannelNode = new EventChannelObjectNode(channel); + for (ChannelMember channelMember : channel.getChannelMembers()) { + if (parentChannelNode.getName() == null) { + Expression message = channelMember.getStateTransition().getMessageExpression(); + if (message instanceof Term) { + parentChannelNode.setName(((Term) message).getSymbol().getName()); + } else if (message instanceof Variable) { + parentChannelNode.setName(((Variable) message).getName()); + } + } + for (ResourceNode destinationResourceNode : dataFlowGraph.getResourceNodes(channelMember.getResource().getResourceHierarchy())) { + StatefulObjectNode destinationNode = pushCallGraph.getStatefulObjectNode(destinationResourceNode); + if (destinationNode == null) { + pushCallGraph.addNode(destinationResourceNode); + destinationNode = pushCallGraph.getStatefulObjectNode(destinationResourceNode); + } + // from an input event channel to a resource + pushCallGraph.insertEdge(parentChannelNode, destinationNode, PushPullValue.PUSH, 0); + } + } + for (Channel child : channel.getChildren()) { traverseChannelHierarchy((DataTransferChannel) child, pushCallGraph); } }