Skip to content

Commit

Permalink
feat: tarjans algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcarman committed Jul 8, 2024
1 parent 9490247 commit 238ff5c
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 42 deletions.
59 changes: 31 additions & 28 deletions src/common/experimental/FlowNodeWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ export class FlowNodeWrapper {
public constructor(typeOfNode: string, node: AnyFlowNode) {
this.type = typeOfNode;
this.typeLabel = getFlowComponentTypeLabel(typeOfNode, node);
this.name = typeOfNode === 'start' ? 'Start' : node.name ?? 'Unknown Node Name';
this.name =
typeOfNode === 'start' ? 'Start' : node.name ?? 'UnknownNode_' + (Math.random() + 1).toString(36).substring(7);
this.label = node.label ?? this.name;
this.location = [node.locationX, node.locationY];
this.data = node;
this.buildConnections(node);
this.buildTerminators();
// this.buildTerminators();
}

private buildConnections(node: AnyFlowNode): void {
Expand All @@ -36,32 +37,32 @@ export class FlowNodeWrapper {
this.addWaitEventConnectors(node);
this.addRuleConnectors(node);
this.addScheduledPaths(node);
this.addTerminatorWhenMissingDefaultConnector(node);
// this.addTerminatorWhenMissingDefaultConnector(node);
}

private buildTerminators(): void {
if (this.connectors.length === 0) {
this.addTerminator();
}
}
// private buildTerminators(): void {
// if (this.connectors.length === 0) {
// this.addTerminator();
// }
// }

private addConnector(connectorType: string, connector: FlowConnector, connectionLabel?: string): void {
this.connectors.push({ type: connectorType, ...connector, connectionLabel });
}

private addTerminator(connectionLabel?: string): void {
const connectorType = 'Terminator';
const connector: FlowConnector = { targetReference: '', processMetadataValues: [{ name: '' }] };
this.connectors.push({ type: connectorType, ...connector, connectionLabel });
}
// private addTerminator(connectionLabel?: string): void {
// const connectorType = 'Terminator';
// const connector: FlowConnector = { targetReference: '', processMetadataValues: [{ name: '' }] };
// this.connectors.push({ type: connectorType, ...connector, connectionLabel });
// }

private addTerminatorWhenMissingDefaultConnector(node: AnyFlowNode): void {
if (this.type === 'decisions') {
if ('defaultConnectorLabel' in node && !('defaultConnector' in node)) {
this.addTerminator(node.defaultConnectorLabel);
}
}
}
// private addTerminatorWhenMissingDefaultConnector(node: AnyFlowNode): void {
// if (this.type === 'decisions') {
// if ('defaultConnectorLabel' in node && !('defaultConnector' in node)) {
// this.addTerminator(node.defaultConnectorLabel);
// }
// }
// }

private addStandardConnector(node: AnyFlowNode): void {
if ('connector' in node && node.connector) {
Expand All @@ -88,10 +89,10 @@ export class FlowNodeWrapper {
private addNextValueConnector(node: AnyFlowNode): void {
if ('nextValueConnector' in node && node.nextValueConnector) {
this.addConnector('nextValueConnector', node.nextValueConnector, 'For Each');
if (!('noMoreValuesConnector' in node)) {
// Does not have a noMoreValuesConnector
this.addTerminator('After Last');
}
// if (!('noMoreValuesConnector' in node)) {
// // Does not have a noMoreValuesConnector
// this.addTerminator('After Last');
// }
}
}

Expand All @@ -107,9 +108,10 @@ export class FlowNodeWrapper {
const connectionLabel = waitEvent.name ? waitEvent.label : undefined;
if (waitEvent.connector) {
this.addConnector('connector', waitEvent.connector, connectionLabel);
} else {
this.addTerminator(connectionLabel);
}
// else {
// this.addTerminator(connectionLabel);
// }
});
}
}
Expand All @@ -120,9 +122,10 @@ export class FlowNodeWrapper {
arrayify(node.rules).forEach((rule) => {
if ('connector' in rule && rule.connector) {
this.addConnector('connector', rule.connector, rule.label);
} else {
this.addTerminator(rule.label);
}
// else {
// this.addTerminator(rule.label);
// }
});
}
}
Expand Down
122 changes: 122 additions & 0 deletions src/common/experimental/FlowWalkerTarjan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { FlowWrapper } from './FlowWrapper.js';

class Node {
public name: string;
public neighbours: Node[];
public index: number;
public lowLink: number;
public onStack: boolean;
public visited: boolean;

public constructor(name: string, neighbours?: Node[]) {
this.name = name;
this.neighbours = neighbours ?? [];
this.index = -1;
this.lowLink = -1;
this.onStack = false;
this.visited = false;
}
}

export class FlowGraph {
public log = 'test';
private stronglyConnectedNodes!: Node[][];
private nodes: Map<string, Node>;

public constructor(flow: FlowWrapper) {
this.nodes = new Map<string, Node>();
this.initNodes(flow);
}

public getNonSingleStronglyConnectedNodes(): Node[][] {
this.execute();
return this.stronglyConnectedNodes.filter((scc) => scc.length > 1);
}

public getStronglyConnectedNodes(): Node[][] {
this.execute();
return this.stronglyConnectedNodes;
}

private initNodes(flow: FlowWrapper): void {
flow.nodes.forEach((flowNode) => {
const nodeName = flowNode.name;
const connectors = flowNode.connectors.map((connector) => connector.targetReference);

// get/create nodes for each connector, and return as neighbours
connectors.forEach((connector) => this.getNode(connector));
const neighbours = connectors.map((connector) => this.nodes.get(connector)!);

// get/create this node, add its neighbours
const node = this.getNode(nodeName);
node.neighbours.push(...neighbours);
});
}

private getNode(name: string): Node {
if (!this.nodes.has(name)) {
const node = new Node(name);
this.nodes.set(name, node);
return node;
}
return this.nodes.get(name)!;
}

private execute(): void {
if (!this.stronglyConnectedNodes) {
this.identifyStronglyConnectedNodes();
}
}

private identifyStronglyConnectedNodes(): this {
console.log('Processing');
let index = 0;
const stack: Node[] = [];
const stronglyConnectedNodes: Node[][] = [];

function stronglyConnected(inputNode: Node): void {
const node = inputNode;
node.index = index;
node.lowLink = index;
index++;
stack.push(node);
node.onStack = true;

node.neighbours.forEach((neighbour) => {
if (neighbour.index === -1) {
stronglyConnected(neighbour);
node.lowLink = Math.min(node.lowLink, neighbour.lowLink);
} else if (neighbour.onStack) {
node.lowLink = Math.min(node.lowLink, neighbour.lowLink);
node.lowLink = Math.min(node.lowLink, neighbour.index);
}
});

if (node.lowLink === node.index) {
const scc: Node[] = [];
let neighbour: Node;
do {
neighbour = stack.pop() as Node;
if (!neighbour) {
break;
}
neighbour.onStack = false;
scc.push(neighbour);
} while (neighbour !== node);

stronglyConnectedNodes.push(scc);
}
}

for (const node of this.nodes.values()) {
if (node.index === -1) {
stronglyConnected(node);
}
}

this.stronglyConnectedNodes = stronglyConnectedNodes;
return this;
}

// public findAllPaths()
}
23 changes: 9 additions & 14 deletions src/rules/no-dml-in-flow-for-loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { RuleClass } from '../common/types.js';
import { parseMetadataXml } from '../common/util.js';
import { FlowWrapper } from '../common/experimental/FlowWrapper.js';
// import { getPaths } from '../common/experimental/FlowWalker.js';
import { generateMermaid } from '../common/experimental/FlowMermaid.js';
// import { generateMermaid } from '../common/experimental/FlowMermaid.js';
import { FlowGraph } from '../common/experimental/FlowWalkerTarjan.js';

export default class NoDmlInFlowForLoop extends RuleClass {
public ruleId: string = 'no-missing-description-on-fields';
Expand All @@ -26,20 +27,14 @@ export default class NoDmlInFlowForLoop extends RuleClass {
// console.dir(flowWrapper, { depth: null });
// walk(flowWrapper);
// const paths = getPaths(flowWrapper);

// let i = 0;
// paths.forEach((path) => {
// i++
// const hash = Md5.hashStr(path.map(entry => entry.nodeName).join(' -> '));
// console.log('i: ', i, 'Hash: ', hash, 'Path: ', path.map(entry => entry.nodeName).join(' -> '));
// console.log('csv: ', path.map(entry => entry.nodeName).join(', '), ',');
// console.log('paths: ', paths.length);
// console.log(path.map(entry => entry.nodeName).join(' -> '));
// });

// console.log(paths);
// console.log('Flow Name: ', flowWrapper.flowName, 'Paths: ', paths.length);
generateMermaid(flowWrapper.nodes);
// generateMermaid(flowWrapper.nodes);
const graph = new FlowGraph(flowWrapper);
const sccs = graph.getNonSingleStronglyConnectedNodes();

sccs.forEach((scc) => {
console.log('SCC: ', scc.map((node) => node.name).join(', '));
});
}
}
}

0 comments on commit 238ff5c

Please sign in to comment.