ObjectTravel is a tool to deeply traverse "native" references of objects through instance variables and "array contents".
Metacello new
baseline: 'ObjectTravel';
repository: 'github://dionisiydk/ObjectTravel';
load.
Usage is quite simple: just create traveler instance on your object and send #referencesDo:
traveler := ObjectTraveler on: (1@2 corner: 3@4).
traveler referencesDo: [:eachRef | eachRef logCr].
It will print on transcript:
(1@2)
(3@4)
1
2
3
4
There is convenience method to collect all references:
traveler collectReferences. "==> an OrderedCollection((1@2) (3@4) 1 2 3 4))
Traveler enumerates each reference in breadth-first direction and visit every reference only once:
point := 1@2.
traveler := ObjectTraveler on: {point . #middle. point}.
traveler collectReferences "==> an OrderedCollection((1@2) #middle (1@2) 1 2)"
"point" is referenced two times. But traveler will go deeply only once. So circles are supported:
array := Array new: 2.
array at: 1 put: 1@3.
array at: 2 put: array.
traveler := ObjectTraveler on: array.
traveler collectReferences asArray = { 1@3. array. 1. 3 } "==> true"
You could control traveler to skip particular objects:
rect := 1@2 corner: 3@4.
traveler := ObjectTraveler on: rect.
traveler skip: rect origin.
traveler collectReferences "==> an OrderedCollection((1@2) (3@4) 3 4)"
(it can be done during enumeration too).
Also you could apply predicate for objects which you want traverse:
rect :=
traveler := ObjectTraveler on: {1@2 corner: 3@4. 5@6}.
traveler := ObjectTraveler on: {1@2 corner: 3@4. 5@6}.
traveler traverseOnly: [:each | each isKindOf: Point].
traveler collectReferences "==> an OrderedCollection((1@2) corner: (3@4) (5@6) 5 6)"
There are another convenient methods:
traveler countReferences
It will count all references in object graph.
traveler copyObject
It will make deep copy of original object which is similar to #veryDeepCopy: cyclic references are supported.
Also traveler could find all paths to given object in object graph:
root := {#one -> #target. {#emptyArray}. {#two -> #target}}.
traveler := ObjectTraveler on: root.
traveler findAllPathsTo: #target "==>
{#one -> #target. root}
{#two -> #target. {#two -> #target}. root} "
During enumeration traveler could replace visited references:
rect := 1@2 corner: 3@4.
traveler := ObjectTraveler on: rect.
traveler referencesDo: [:each |
each isInteger ifTrue: [traveler replaceCurrentReferenceWith: each + 1]].
rect "==> 2@3 corner: 4@5"
It would be fun project to connect ObjectTraveler to XStreams to make any object "streamable":
(1@2 corner: 3@4) traveling reading count: [:each | each isInteger].
(1@2 corner: 3@4) traveling reading get; get; read: 2; rest.
(1@2 corner: 3@4) traveling writing write: 100@500
Update: project was moved to github
Update: project was moved to github