All Implemented Interfaces:
Iterable<FlowNode>, Iterator<FlowNode>, Filterator<FlowNode>

@NotThreadSafe public class ForkScanner extends AbstractFlowScanner
Scanner that will scan down all forks when we hit parallel blocks before continuing (as opposed to DepthFirstScanner), but generally runs in linear order.

Warning This scanner has various issues when iterating over incomplete builds, or more generally when multiple heads are passed to AbstractFlowScanner.setup(java.util.Collection<org.jenkinsci.plugins.workflow.graph.FlowNode>, java.util.Collection<org.jenkinsci.plugins.workflow.graph.FlowNode>), especially if the build contains nested parallelism. Consider using DepthFirstScanner instead in those cases to avoid these issues, employing its iteration order as needed to sort results (although its global iteration order is inconsistent, its relative iteration order among siblings at the same nesting level (e.g. parallel branches) is consistent and that is good enough for typical use cases such as producing a tree). In particular, the following known issues are possible when using this scanner with multiple heads:

For completed builds, or when a single head is passed to AbstractFlowScanner.setup(java.util.Collection<org.jenkinsci.plugins.workflow.graph.FlowNode>, java.util.Collection<org.jenkinsci.plugins.workflow.graph.FlowNode>), the above issues should not occur and the following description should be accurate:

This is a fairly efficient way to visit all FlowNodes, and provides four useful guarantees:

  • Every FlowNode is visited, and visited EXACTLY ONCE (not true for LinearScanner, which misses some)
  • All parallel branches are visited before we move past the parallel block (not true for DepthFirstScanner)
  • For parallels, we visit branches in reverse order (in fitting with end to start general flow)
  • For EVERY block, the BlockEndNode is visited before the BlockStartNode (not true for DepthFirstScanner, with parallels)

The big advantages of this approach:

  • Blocks are visited in the order they end (no backtracking) - helps with working a block at a time
  • Points are visited in linear order within a block (easy to use for analysis)
  • Minimal state information needed
  • Branch information is available for use here
Sam Van Oort
  • Constructor Details

  • Method Details

    • getCurrentType

      @CheckForNull public org.jenkinsci.plugins.workflow.graphanalysis.ForkScanner.NodeType getCurrentType()
    • getNextType

      @CheckForNull public org.jenkinsci.plugins.workflow.graphanalysis.ForkScanner.NodeType getNextType()
    • reset

      protected void reset()
      Description copied from class: AbstractFlowScanner
      Reset internal state so that we can begin walking a new flow graph Public APIs need to invoke this before searches
      Specified by:
      reset in class AbstractFlowScanner
    • setParallelStartPredicate

      @Deprecated public static void setParallelStartPredicate(@NonNull<FlowNode> pred)
      Now a complete no-op -- originally this was a workaround for dependency issues with workflow-cps. Specifically, requiring classes from workflow-cps to detect if something is a parallel step.
    • isParallelStart

      public static boolean isParallelStart(@CheckForNull FlowNode f)
    • isParallelEnd

      public static boolean isParallelEnd(@CheckForNull FlowNode f)
    • isWalkingFromFinish

      public boolean isWalkingFromFinish()
      If true, we are walking from the flow end node and have a complete view of the flow Needed because there are implications when not walking from a finished flow (blocks without a BlockEndNode)
    • setHeads

      protected void setHeads(@NonNull Collection<FlowNode> heads)
      Description copied from class: AbstractFlowScanner
      Set up to begin flow scanning using the filteredHeads as starting points This method makes several assumptions: - AbstractFlowScanner.reset() has already been invoked to reset state - filteredHeads has already had any points in AbstractFlowScanner.myBlackList removed - none of the filteredHeads are null
      Specified by:
      setHeads in class AbstractFlowScanner
      heads - Head nodes that have been filtered against denyList
    • getCurrentParallelStartNode

      @CheckForNull public FlowNode getCurrentParallelStartNode()
      Return the node that begins the current parallel head, if we are known to be in a parallel block
      The FlowNode that marks current parallel start
    • getParallelDepth

      public int getParallelDepth()
      Return number of levels deep we are in parallel blocks
    • next

      public FlowNode next()
      Specified by:
      next in interface Iterator<FlowNode>
      next in class AbstractFlowScanner
    • next

      protected FlowNode next(@NonNull FlowNode current, @NonNull Collection<FlowNode> blackList)
      Description copied from class: AbstractFlowScanner
      Actual meat of the iteration, get the next node to visit, using and updating state as needed
      Specified by:
      next in class AbstractFlowScanner
      current - Current node to use in generating next value
      blackList - Nodes that are not eligible for visiting
      Next node to visit, or null if we've exhausted the node list
    • visitSimpleChunks

      public static void visitSimpleChunks(@NonNull Collection<FlowNode> heads, @NonNull Collection<FlowNode> blacklist, @NonNull SimpleChunkVisitor visitor, @NonNull ChunkFinder finder)
    • visitSimpleChunks

      public static void visitSimpleChunks(@NonNull Collection<FlowNode> heads, @NonNull SimpleChunkVisitor visitor, @NonNull ChunkFinder finder)
    • visitSimpleChunks

      public void visitSimpleChunks(@NonNull SimpleChunkVisitor visitor, @NonNull ChunkFinder finder)
      Walk through flows