diff --git a/src/common/experimental/FlowNodeWrapper.ts b/src/common/experimental/FlowNodeWrapper.ts index 63515d6..24a1698 100644 --- a/src/common/experimental/FlowNodeWrapper.ts +++ b/src/common/experimental/FlowNodeWrapper.ts @@ -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 { @@ -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) { @@ -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'); + // } } } @@ -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); + // } }); } } @@ -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); + // } }); } } diff --git a/src/common/experimental/FlowWalkerTarjan.ts b/src/common/experimental/FlowWalkerTarjan.ts new file mode 100644 index 0000000..7ea7de8 --- /dev/null +++ b/src/common/experimental/FlowWalkerTarjan.ts @@ -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; + + public constructor(flow: FlowWrapper) { + this.nodes = new Map(); + 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() +} diff --git a/src/rules/no-dml-in-flow-for-loop.ts b/src/rules/no-dml-in-flow-for-loop.ts index bd68c97..e21e00b 100644 --- a/src/rules/no-dml-in-flow-for-loop.ts +++ b/src/rules/no-dml-in-flow-for-loop.ts @@ -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'; @@ -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(', ')); + }); } } }