9
9
import java .util .Collections ;
10
10
import java .util .LinkedHashMap ;
11
11
import java .util .List ;
12
+ import java .util .Map ;
12
13
import org .jenkinsci .plugins .workflow .cps .nodes .StepAtomNode ;
13
14
import org .jenkinsci .plugins .workflow .graph .BlockEndNode ;
15
+ import org .jenkinsci .plugins .workflow .graph .BlockStartNode ;
14
16
import org .jenkinsci .plugins .workflow .graph .FlowNode ;
15
17
import org .slf4j .Logger ;
16
18
import org .slf4j .LoggerFactory ;
@@ -20,16 +22,24 @@ public class NodeRelationshipFinder {
20
22
private boolean isDebugEnabled = logger .isDebugEnabled ();
21
23
22
24
private LinkedHashMap <String , FlowNode > endNodes = new LinkedHashMap <>();
23
- private ArrayDeque <FlowNode > pendingEndNodes = new ArrayDeque <>();
24
- private ArrayDeque <FlowNode > pendingStartNodes = new ArrayDeque <>();
25
25
26
- // Somewhere to temporarily store the parallel branches information whilst we
27
- // are handing a parallel block.
28
- // The StatusAndTiming API requires us to the structure of the parallel block to
29
- // get the status of a branch.
26
+ /* Stack of stacks to store the last seen node for each nested block we have gone into.
27
+ * Used to assign the after node for relationships.
28
+ * This can be a different type, depending on situation:
29
+ * - For the last Atom node in a block, will be the BlockEndNode (or null if the step is running).
30
+ * - For nodes that have later siblings, this will be the previous sibling we found.
31
+ * - This might be a AtomNode or a BlockStartNode (when a step is followed by a StepBlock).
32
+ */
33
+ private ArrayDeque <ArrayDeque <FlowNode >> lastSeenNodes = new ArrayDeque <>();
34
+ private Map <String , ArrayDeque <FlowNode >> seenChildNodes = new LinkedHashMap <>();
35
+
36
+ /* Somewhere to temporarily store the parallel branches information whilst we
37
+ * are handing a parallel block.
38
+ * The StatusAndTiming API requires us to the structure of the parallel block to
39
+ * get the status of a branch.
40
+ */
30
41
private ArrayDeque <NodeRelationship > pendingBranchRelationships = new ArrayDeque <>();
31
42
32
- private NodeRelationship subsequentStepRelationship = null ;
33
43
private LinkedHashMap <String , NodeRelationship > relationships = new LinkedHashMap <>();
34
44
35
45
// Print debug message if 'isDebugEnabled' is true.
@@ -57,6 +67,9 @@ public LinkedHashMap<String, NodeRelationship> getNodeRelationships(
57
67
dump ("Sorted Ids: %s" , String .join (", " , sortedIds ));
58
68
for (String id : sortedIds ) {
59
69
getRelationshipForNode (nodeMap .get (id ));
70
+ // Add this node to the parents's stack as the last of it's child nodes that
71
+ // we have seen.
72
+ addSeenNodes (nodeMap .get (id ));
60
73
}
61
74
return relationships ;
62
75
}
@@ -73,8 +86,6 @@ private void getRelationshipForNode(@NonNull FlowNode node) {
73
86
}
74
87
75
88
private void handleBlockStart (@ NonNull FlowNode node ) {
76
- // Reset step relationship - shouldn't carry over between blocks.
77
- subsequentStepRelationship = null ;
78
89
// Assign end node to start node.
79
90
if (FlowNodeWrapper .isStart (node )) {
80
91
addBlockRelationship (node );
@@ -83,32 +94,68 @@ private void handleBlockStart(@NonNull FlowNode node) {
83
94
}
84
95
}
85
96
86
- private void addStepRelationship (@ NonNull StepAtomNode step ) {
87
- dump ("Generating relationship for step %s" , step .getId ());
97
+ private void addSeenNodes (FlowNode node ) {
98
+ if (!seenChildNodes .keySet ().contains (node .getEnclosingId ())) {
99
+ seenChildNodes .put (node .getEnclosingId (), new ArrayDeque <FlowNode >());
100
+ }
101
+ dump ("Adding %s to seenChildNodes %s" , node .getId (), node .getEnclosingId ());
102
+ seenChildNodes .get (node .getEnclosingId ()).push (node );
103
+ }
104
+
105
+ @ CheckForNull
106
+ private FlowNode getAfterNode (FlowNode node ) {
88
107
FlowNode after = null ;
89
- // If we are followed by a step, that will be our after node, otherwise use the
90
- // end node of the block.
91
- if (subsequentStepRelationship != null ) {
92
- after = subsequentStepRelationship .getStart ();
93
- dump ("Getting after node (%s) from subsequentStepRelationship" , after .getId ());
108
+ // The after node is the last child of the enclosing node, except for the last node in
109
+ // a block, then it's the last node in the enclosing nodes list (likely, this blocks end node).
110
+ FlowNode parentStartNode = getFirstEnclosingNode (node );
111
+ ArrayDeque <FlowNode > laterSiblings = getProcessedChildren (parentStartNode );
112
+ if (parentStartNode != null && laterSiblings .isEmpty ()) {
113
+ // If there are no later siblings, get the parents later sibling.
114
+ ArrayDeque <FlowNode > parentsLaterSiblings = getProcessedChildren (getFirstEnclosingNode (parentStartNode ));
115
+ after = parentsLaterSiblings .isEmpty () ? null : parentsLaterSiblings .peek ();
116
+ dump (parentsLaterSiblings .toString ());
94
117
} else {
95
- after = pendingEndNodes .peek ();
96
- dump (
97
- "Getting after node (%s) from endNodes stack (size: %s)" ,
98
- (after != null ) ? after .getId () : "null" , endNodes .size ());
118
+ dump (laterSiblings .toString ());
119
+ after = laterSiblings .peek ();
99
120
}
121
+ return after ;
122
+ }
123
+
124
+ @ CheckForNull
125
+ private BlockStartNode getFirstEnclosingNode (FlowNode node ) {
126
+ return node .getEnclosingBlocks ().isEmpty ()
127
+ ? null
128
+ : node .getEnclosingBlocks ().get (0 );
129
+ }
130
+
131
+ private ArrayDeque <FlowNode > getProcessedChildren (@ CheckForNull FlowNode node ) {
132
+ if (node != null && seenChildNodes .keySet ().contains (node .getId ())) {
133
+ return seenChildNodes .get (node .getId ());
134
+ }
135
+ return new ArrayDeque <FlowNode >();
136
+ }
137
+
138
+ private void addStepRelationship (@ NonNull StepAtomNode step ) {
139
+ dump ("Generating relationship for step %s" , step .getId ());
140
+ // FlowNode after = subsequentNode;
141
+ FlowNode after = getAfterNode (step );
142
+ dump (
143
+ "Adding step for %s(%s),%s(%s)" ,
144
+ step .getId (),
145
+ step .getClass ().getName (),
146
+ after == null ? "null" : after .getId (),
147
+ after == null ? "null" : after .getClass ().getName ());
100
148
NodeRelationship nodeRelationship = new NodeRelationship (step , step , after );
101
149
relationships .put (step .getId (), nodeRelationship );
102
- subsequentStepRelationship = nodeRelationship ;
103
150
}
104
151
105
152
private void handleBlockEnd (@ NonNull BlockEndNode <?> endNode ) {
106
153
// Blindly push a new start pending reliable way to check for parallel node.
107
154
FlowNode startNode = endNode .getStartNode ();
108
155
endNodes .put (startNode .getId (), endNode );
109
- pendingEndNodes . push ( endNode );
110
- dump ( "Adding %s to pendingEndNodes" , endNode . getId () );
111
- pendingStartNodes .push (startNode );
156
+ // Create new stack for this block, add the end node and push it to stack of stacks.
157
+ ArrayDeque < FlowNode > nodeBlockStack = new ArrayDeque <>( );
158
+ lastSeenNodes .push (nodeBlockStack );
112
159
}
113
160
114
161
private void addBlockRelationship (@ NonNull FlowNode node ) {
@@ -131,15 +178,11 @@ private void addBlockRelationship(@NonNull FlowNode node) {
131
178
if (endNode != node ) {
132
179
relationships .put (endNode .getId (), blockRelationship );
133
180
}
134
- // Remove end node from stack (if there is one).
135
- if (!pendingEndNodes .isEmpty ()) {
136
- pendingEndNodes .pop ();
137
- }
138
181
}
139
182
}
140
183
141
184
private void addParallelBranchRelationship (@ NonNull FlowNode node , @ NonNull FlowNode endNode ) {
142
- FlowNode after = getBlockAfterNode ( );
185
+ FlowNode after = getAfterNode ( node );
143
186
// Store a parallel branch relationship - these will be used to build up the
144
187
// parent parallel block relationship.
145
188
// Once generated, that relationship will be superseded this one.
@@ -156,7 +199,7 @@ private void addParallelBranchRelationship(@NonNull FlowNode node, @NonNull Flow
156
199
}
157
200
158
201
private NodeRelationship addParallelRelationship (@ NonNull FlowNode node , @ NonNull FlowNode endNode ) {
159
- FlowNode after = getBlockAfterNode ( );
202
+ FlowNode after = getAfterNode ( node );
160
203
dump (
161
204
"Generating relationship for parallel Block %s (with after %s)" ,
162
205
node .getId (), (after != null ) ? after .getId () : "null" );
@@ -176,20 +219,8 @@ private NodeRelationship addParallelRelationship(@NonNull FlowNode node, @NonNul
176
219
return parallelRelationship ;
177
220
}
178
221
179
- @ CheckForNull
180
- private FlowNode getBlockAfterNode () {
181
- FlowNode after = null ;
182
- if (!pendingEndNodes .isEmpty ()) {
183
- after = pendingEndNodes .pop ();
184
- }
185
- dump (
186
- "Getting after node (%s) from pendingEndNodes stack (size: %s)" ,
187
- (after != null ) ? after .getId () : "null" , pendingEndNodes .size ());
188
- return after ;
189
- }
190
-
191
222
private NodeRelationship addStageRelationship (@ NonNull FlowNode node , @ NonNull FlowNode endNode ) {
192
- FlowNode after = getBlockAfterNode ( );
223
+ FlowNode after = getAfterNode ( node );
193
224
dump (
194
225
"Generating relationship for Block %s{%s}->%s{%s} (with after %s{%s})" ,
195
226
node .getId (),
0 commit comments