Class AbstractFlowScanner
- java.lang.Object
-
- org.jenkinsci.plugins.workflow.graphanalysis.AbstractFlowScanner
-
- Direct Known Subclasses:
DepthFirstScanner
,ForkScanner
,LinearScanner
@NotThreadSafe public abstract class AbstractFlowScanner extends Object implements Iterable<FlowNode>, Filterator<FlowNode>
Core APIs and base logic for FlowScanners that extract information from a pipeline execution.These iterate through the directed acyclic graph (DAG) or "flow graph" of
FlowNode
s produced when a pipeline runs.This provides 6 base APIs to use, in decreasing expressiveness and increasing genericity:
findFirstMatch(Collection, Collection, Predicate)
: find the first FlowNode matching predicate condition.filteredNodes(Collection, Collection, Predicate)
: return the collection of FlowNodes matching the predicate.visitAll(Collection, FlowNodeVisitor)
: given aFlowNodeVisitor
, invokeFlowNodeVisitor.visit(FlowNode)
on each node and halt when it returns false.- Iterator: Each FlowScanner can be used as an Iterator for FlowNode-by-FlowNode walking,
after you invoke
setup(Collection, Collection)
to initialize it for iteration. Filterator
: If initialized as an Iterator, each FlowScanner can provide a filtered view from the current point in time.- Iterable: for syntactic sugar, FlowScanners implement Iterable to allow use in for-each loops once initialized.
All APIs visit the parent nodes, walking backward from heads(inclusive) until they they hit
myBlackList
nodes (exclusive) or reach the end of the DAG. If denyList nodes are an empty collection or null, APIs will walk to the beginning of the FlowGraph. Multiple denyList nodes are helpful for putting separate bounds on walking different parallel branches.Key Points:
- There are many helper methods offering syntactic sugar for the above APIs in common use cases (simpler method signatures).
- Each implementation provides its own iteration order (described in its javadoc comments), but it is generally unsafe to rely on parallel branches being visited in a specific order.
- Implementations may visit some or all points in the DAG, this should be called out in the class's javadoc comments
- FlowScanners are NOT thread safe, for performance reasons and because it is too hard to guarantee.
- Many fields and methods are protected: this is intentional to allow building upon the implementations for more complex analyses.
- Each FlowScanner stores state internally for several reasons:
- This state can be used to construct more advanced analyses.
- FlowScanners can be reinitialized and reused repeatedly: avoids the overheads of creating scanners repeatedly.
- Allows for caching to be added inside a FlowScanner if desired, but caching is only useful when reused.
Suggested uses:
- Implement a
FlowNodeVisitor
that collects metrics from each FlowNode visited, and call visitAll to extract the data. - Find all flownodes of a given type (ex: stages), using
filteredNodes(Collection, Collection, Predicate)
- Find the first node with an
ErrorAction
before a specific node - Scan through all nodes *just* within a block
- Use the
BlockEndNode
as the head - Use the
BlockStartNode
as its denylist withCollections.singleton(Object)
- Use the
- Author:
- Sam Van Oort
-
-
Field Summary
Fields Modifier and Type Field Description protected static int
MAX_LIST_CHECK_SIZE
When checking for denylist membership, we convert to a hashset when checking more than this many elementsprotected Collection<FlowNode>
myBlackList
protected FlowNode
myCurrent
protected FlowNode
myNext
-
Constructor Summary
Constructors Constructor Description AbstractFlowScanner()
-
Method Summary
All Methods Instance Methods Abstract Methods Concrete Methods Modifier and Type Method Description List<FlowNode>
allNodes(Collection<FlowNode> heads)
Convenience method to get the list all flownodes in the iterator order.List<FlowNode>
allNodes(FlowExecution exec)
Convenience method to get the list of allFlowNode
s for the execution, in iterator order.protected Collection<FlowNode>
convertToFastCheckable(Collection<FlowNode> nodeCollection)
Helper: convert stop nodes to a collection that can efficiently be checked for membership, handling null if neededFilterator<FlowNode>
filter(com.google.common.base.Predicate<FlowNode> filterCondition)
Expose a filtered view of this FlowScanner's output.List<FlowNode>
filteredNodes(Collection<FlowNode> heads, com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfilteredNodes(Collection, Collection, Predicate)
with no denyList nodesList<FlowNode>
filteredNodes(Collection<FlowNode> heads, Collection<FlowNode> blackList, com.google.common.base.Predicate<FlowNode> matchCondition)
Return a filtered list ofFlowNode
s matching a condition, in the order encountered.List<FlowNode>
filteredNodes(FlowExecution exec, com.google.common.base.Predicate<FlowNode> matchPredicate)
List<FlowNode>
filteredNodes(FlowNode head, com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfilteredNodes(Collection, Collection, Predicate)
with a single head and no denyList nodesFlowNode
findFirstMatch(Collection<FlowNode> heads, com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfindFirstMatch(Collection, Collection, Predicate)
where there is no denyListFlowNode
findFirstMatch(Collection<FlowNode> heads, Collection<FlowNode> blackListNodes, com.google.common.base.Predicate<FlowNode> matchCondition)
Find the first FlowNode within the iteration order matching a given condition Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use).FlowNode
findFirstMatch(FlowExecution exec, com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfindFirstMatch(Collection, Collection, Predicate)
usingFlowExecution.getCurrentHeads()
to get heads and no denyListFlowNode
findFirstMatch(FlowNode head, com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfindFirstMatch(Collection, Collection, Predicate)
where there is a single head and no denyListboolean
hasNext()
Iterator<FlowNode>
iterator()
FlowNode
next()
protected abstract FlowNode
next(FlowNode current, Collection<FlowNode> blackList)
Actual meat of the iteration, get the next node to visit, using and updating state as neededvoid
remove()
protected abstract void
reset()
Reset internal state so that we can begin walking a new flow graph Public APIs need to invoke this before searchesprotected abstract void
setHeads(Collection<FlowNode> filteredHeads)
Set up to begin flow scanning using the filteredHeads as starting points This method makes several assumptions: -reset()
has already been invoked to reset state - filteredHeads has already had any points inmyBlackList
removed - none of the filteredHeads are nullboolean
setup(Collection<FlowNode> heads)
Helper: version ofsetup(Collection, Collection)
where we don't have any nodes to denylistboolean
setup(Collection<FlowNode> heads, Collection<FlowNode> blackList)
Set up for iteration/analysis on a graph of nodes, initializing the internal state Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use).boolean
setup(FlowNode head)
Helper: version ofsetup(Collection, Collection)
where we don't have any nodes to denylist and have just a single headboolean
setup(FlowNode head, Collection<FlowNode> blackList)
Helper: version ofsetup(Collection, Collection)
where we don't have any nodes to denylist, and have just a single headvoid
visitAll(Collection<FlowNode> heads, Collection<FlowNode> blackList, FlowNodeVisitor visitor)
Given aFlowNodeVisitor
, invokeFlowNodeVisitor.visit(FlowNode)
on each node and halt early if it returns false.void
visitAll(Collection<FlowNode> heads, FlowNodeVisitor visitor)
Syntactic sugar forvisitAll(Collection, Collection, FlowNodeVisitor)
where we don't denylist any nodes-
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
-
Methods inherited from interface java.lang.Iterable
forEach, spliterator
-
Methods inherited from interface java.util.Iterator
forEachRemaining
-
-
-
-
Field Detail
-
myCurrent
protected FlowNode myCurrent
-
myNext
protected FlowNode myNext
-
myBlackList
protected Collection<FlowNode> myBlackList
-
MAX_LIST_CHECK_SIZE
protected static final int MAX_LIST_CHECK_SIZE
When checking for denylist membership, we convert to a hashset when checking more than this many elements- See Also:
- Constant Field Values
-
-
Method Detail
-
convertToFastCheckable
@NonNull protected Collection<FlowNode> convertToFastCheckable(@CheckForNull Collection<FlowNode> nodeCollection)
Helper: convert stop nodes to a collection that can efficiently be checked for membership, handling null if needed
-
setup
public boolean setup(@CheckForNull Collection<FlowNode> heads, @CheckForNull Collection<FlowNode> blackList)
Set up for iteration/analysis on a graph of nodes, initializing the internal state Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use).- Parameters:
heads
- The head nodes we start walking from (the most recently executed nodes, i.e. FlowExecution.getCurrentHeads()blackList
- Nodes that we cannot visit or walk past (useful to limit scanning to only nodes after a specific point)- Returns:
- True if we can have nodes to work with, otherwise false
-
setup
public boolean setup(@CheckForNull Collection<FlowNode> heads)
Helper: version ofsetup(Collection, Collection)
where we don't have any nodes to denylist
-
setup
public boolean setup(@CheckForNull FlowNode head, @CheckForNull Collection<FlowNode> blackList)
Helper: version ofsetup(Collection, Collection)
where we don't have any nodes to denylist, and have just a single head
-
setup
public boolean setup(@CheckForNull FlowNode head)
Helper: version ofsetup(Collection, Collection)
where we don't have any nodes to denylist and have just a single head
-
reset
protected abstract void reset()
Reset internal state so that we can begin walking a new flow graph Public APIs need to invoke this before searches
-
setHeads
protected abstract void setHeads(@NonNull Collection<FlowNode> filteredHeads)
Set up to begin flow scanning using the filteredHeads as starting points This method makes several assumptions: -reset()
has already been invoked to reset state - filteredHeads has already had any points inmyBlackList
removed - none of the filteredHeads are null- Parameters:
filteredHeads
- Head nodes that have been filtered against denyList
-
next
@CheckForNull protected abstract FlowNode next(@NonNull FlowNode current, @NonNull Collection<FlowNode> blackList)
Actual meat of the iteration, get the next node to visit, using and updating state as needed- Parameters:
current
- Current node to use in generating next valueblackList
- Nodes that are not eligible for visiting- Returns:
- Next node to visit, or null if we've exhausted the node list
-
filter
@NonNull public Filterator<FlowNode> filter(@NonNull com.google.common.base.Predicate<FlowNode> filterCondition)
Expose a filtered view of this FlowScanner's output.- Specified by:
filter
in interfaceFilterator<FlowNode>
- Parameters:
filterCondition
- Filterator only returnsFlowNode
s matching this predicate.- Returns:
- A
Filterator
against this FlowScanner, which can be filtered in additional ways.
-
findFirstMatch
@CheckForNull public FlowNode findFirstMatch(@CheckForNull Collection<FlowNode> heads, @CheckForNull Collection<FlowNode> blackListNodes, com.google.common.base.Predicate<FlowNode> matchCondition)
Find the first FlowNode within the iteration order matching a given condition Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use).- Parameters:
heads
- Head nodes to start walking fromblackListNodes
- Nodes that are never visited, search stops here (bound is exclusive). If you want to create an inclusive bound, just use a node's parents.matchCondition
- Predicate to match when we've successfully found a given node type- Returns:
- First matching node, or null if no matches found
-
findFirstMatch
@CheckForNull public FlowNode findFirstMatch(@CheckForNull Collection<FlowNode> heads, @NonNull com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfindFirstMatch(Collection, Collection, Predicate)
where there is no denyList
-
findFirstMatch
@CheckForNull public FlowNode findFirstMatch(@CheckForNull FlowNode head, @NonNull com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfindFirstMatch(Collection, Collection, Predicate)
where there is a single head and no denyList
-
findFirstMatch
@CheckForNull public FlowNode findFirstMatch(@CheckForNull FlowExecution exec, @NonNull com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfindFirstMatch(Collection, Collection, Predicate)
usingFlowExecution.getCurrentHeads()
to get heads and no denyList
-
filteredNodes
@NonNull public List<FlowNode> filteredNodes(@CheckForNull Collection<FlowNode> heads, @CheckForNull Collection<FlowNode> blackList, com.google.common.base.Predicate<FlowNode> matchCondition)
Return a filtered list ofFlowNode
s matching a condition, in the order encountered. Includes null-checking on arguments to allow directly calling with unchecked inputs (simplifies use).- Parameters:
heads
- Nodes to start iterating backward from by visiting their parents.blackList
- Nodes we may not visit or walk beyond.matchCondition
- Predicate that must be met for nodes to be included in output. Input is always non-null.- Returns:
- List of flownodes matching the predicate.
-
allNodes
@NonNull public List<FlowNode> allNodes(@CheckForNull Collection<FlowNode> heads)
Convenience method to get the list all flownodes in the iterator order.
-
allNodes
@NonNull public List<FlowNode> allNodes(@CheckForNull FlowExecution exec)
Convenience method to get the list of allFlowNode
s for the execution, in iterator order.
-
filteredNodes
@NonNull public List<FlowNode> filteredNodes(@CheckForNull Collection<FlowNode> heads, @NonNull com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfilteredNodes(Collection, Collection, Predicate)
with no denyList nodes
-
filteredNodes
@NonNull public List<FlowNode> filteredNodes(@CheckForNull FlowNode head, @NonNull com.google.common.base.Predicate<FlowNode> matchPredicate)
Syntactic sugar forfilteredNodes(Collection, Collection, Predicate)
with a single head and no denyList nodes
-
filteredNodes
@NonNull public List<FlowNode> filteredNodes(@CheckForNull FlowExecution exec, @NonNull com.google.common.base.Predicate<FlowNode> matchPredicate)
-
visitAll
public void visitAll(@CheckForNull Collection<FlowNode> heads, @CheckForNull Collection<FlowNode> blackList, @NonNull FlowNodeVisitor visitor)
Given aFlowNodeVisitor
, invokeFlowNodeVisitor.visit(FlowNode)
on each node and halt early if it returns false. Includes null-checking on all but the visitor, to allow directly calling with unchecked inputs (simplifies use). Useful if you wish to collect some information from every node in the FlowGraph. To do that, accumulate internal state in the visitor, and invoke a getter when complete.- Parameters:
heads
- Nodes to start walking the DAG backwards from.blackList
- Nodes we can't visit or pass beyond.visitor
- Visitor that will see each FlowNode encountered.
-
visitAll
public void visitAll(@CheckForNull Collection<FlowNode> heads, @NonNull FlowNodeVisitor visitor)
Syntactic sugar forvisitAll(Collection, Collection, FlowNodeVisitor)
where we don't denylist any nodes
-
-