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.
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 horizontalDominanceTree.
m
Of course, once we have the basic graph in place, we can extend the visualization with more embellishments:
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 horizontalDominanceTree
levelDistance: 50;
nodeDistance: 20.
m
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:.