Traversing graphs with DeepTraverser
Objects can form complicated graphs. To understand graphs, you need to traverse them.
Indeed, this need is often observed in traversal methods directly written inside the domain classes. For example, to get all subclasses of a given class we have Behavior>>#allSubclassesDo:
. For traversing the children of an element we have BlElement>>#allChildrenDepthFirstDo:
. These are great, but what do we do when we don't have these?
Enter DeepTraverser, a scriptable traversal engine.
For example, to get all subclasses of Collection
, we can do:
all := OrderedCollection new. Collection deep: [:class | class subclasses ] do: [ :class | all add: class ]. all
The Object>>#deep:do:
is a generic traversal that can be applied to any object and specifices two parts:
The way to go from one object to the next objects in the graph, and
What to do for each traversed object.
Example: getting the object graph
Say we have a Bloc element:
element := BlElement new
What objects is it made of? To find this out, we can traverse all objects reachable from this object that are part of packages named Bloc
and collect them in a collection. For this we use Object>>#withDeepCollect:
BlElement new withDeepCollect: [ :each | each class instVarNames collect: [:iv | each instVarNamed: iv] thenSelect: [:object | object class package name beginsWith: 'Bloc' ] ]
Having the list is nice. But, how are the objects composed? A graph would be better. For this we need both to get the nodes and the edges using yet another traversal Object>>#withDeep:do:relationDo:
. To build the actual graph we use Mondrian:
m := GtMondrian new. BlElement new withDeep: [ :each | each class instVarNames collect: [ :iv | each instVarNamed: iv ] thenSelect: [ :object | object class package name beginsWith: 'Bloc' ] ] do: [ :each | m nodes with: {each} ] relationDo: [ :from :to | m edges connectAssociations: {from -> to} ]. m layout custom: GtGraphHorizontalTreeLayout new. m
Of course, once we have the basic graph in place, we can extend the visualization with more ebellishments:
m := GtMondrian new. BlElement new withDeep: [ :each | each class instVarNames collect: [ :iv | each instVarNamed: iv ] thenSelect: [ :object | object class package name beginsWith: 'Bloc' ] ] do: [ :each | m nodes stencil: [ :x | BrVerticalPane new fitContent; addChild: (BrLabel new text: x class name; aptitude: (BrGlamorousLabelAptitude new foreground: Color gray; fontSize: 10)); addChild: (BrLabel new text: x gtDisplayString; aptitude: BrGlamorousLabelAptitude new) ]; with: {each} ] relationDo: [ :from :to | m edges fromRightCenter; toLeftCenter; stencil: [ :x | BlParabollaArcElement new zIndex: 0; curvatureFraction: 0.3; border: (BlBorder paint: (Color gray alpha: 0.2) width: 2); toHead: (BlArrowheadSimpleArrow new border: (BlBorder builder paint: (Color gray alpha: 0.2); width: 4; build)) ]; connectAssociations: {from -> to} ]. m layout custom: (GtGraphHorizontalTreeLayout new levelDistance: 50; nodeDistance: 20). m
Traversal API
The main class is DeepTraverser
. It can be configured in several ways. But the more convenient API is found directly Object
. Look for methods starting with deep:
.