I don’t have any code today, because I was working the polls yesterday, and spent most of today’s worktime tuning that file copier. I hit a big problem because of some confusing, mis-coded loops. Here’s the scenario in pseudocode (I don’t want to fire up the Windows box just to get this code).
for each item in collectionA
if type of item is Layer then
processLayer item
else if type of item is LayerGroup then
for each subItem in item
if type of subItem is Layer then
processLayer subItem
else if type of subItem is Raster
processRaster subItem
end if
end for
end if
end for
If you can really read this, it does a 1-level deep exploration when the item is a LayerGroup.
It’s implid that an item can be a Layer, a LayerGroup, or a Raster.
Well, the problem was that sometimes, Rasters were at the top level. Oooops. The fix is to use a recursive function. A recursive function is a function that calls itself.
That definition usually doesn’t explain, too well, to most people, what a recursive function is. A recursive function, in this situation, is used to process a hierarchy of objects. In the same way that a loop is useful to process a list of objects, a recursive function is useful to process a hierarchy of objects. Whenever we have an object that contains other objects, the recursive function is used on that object.
Here’s the new code:
define function F( collectionA )
for each item in collectionA
if type of item is Layer then
processLayer item
else if type of item is Raster then
processRaster item
else if type of item is LayerGroup then
F( item )
end if
end for
end for
This way of doing it is not just more compact, it’s also lacking the bug. It will also process not only two-layer hierarchies: it’ll handle all hierarchies.
Now, if you already write recursive functions, you may not believe this, but the original algorithm above was being used. It’s just more evidence that it’s still pretty hard to grasp recursion. It doesn’t really make sense until you use it to do something like seek files in a directory hierarchy. Even then, it might not make sense to everyone.
These days, recursion is not so common anymore, because a lot of apps use database tables rather than hierarchies of files.
Denouement
There’s a slightly unhappy “undoing” to this lesson. The problem is that the type of the initial collection differs from the type of the collection within the hierarchy. I operated under the assumption that I could use the Object object to hold the collection. In fact, I had to coerce the object to one of two types to do what I wished.
My error requires (as far as I know) that I, basically, duplicate code, with one for each type of collection. Perhaps there’s a solution available using generic programming, and that I just don’t know it yet.
Here’s the code:
Public Function ProcessLayerSourceCollection(ByVal lsc As Object) As Collection
Dim cOutput As Collection = New Collection()
Dim pFeatureLayer As IFeatureLayer
Dim pRasterLayer As IRasterLayer
Dim pCompositeLayer As ICompositeLayer
Dim s As String
Dim i As Integer
If TypeOf lsc Is ICompositeLayer Then
Dim c As ICompositeLayer = lsc
For i = 0 To c.Count - 1
If TypeOf c.Layer(i) Is IFeatureLayer Then
pFeatureLayer = c.Layer(i)
For Each s In LayerSources(pFeatureLayer)
cOutput.Add(s)
Next
ElseIf TypeOf c.Layer(i) Is IRasterLayer Then
pRasterLayer = c.Layer(i)
For Each s In RasterSources(pRasterLayer)
cOutput.Add(s)
Next
For Each s In InfoDirContents(pRasterLayer.FilePath())
cOutput.Add(s)
Next
ElseIf TypeOf c.Layer(i) Is ICompositeLayer Then
pCompositeLayer = c.Layer(i)
For Each s In ProcessLayerSourceCollection(pCompositeLayer)
cOutput.Add(s)
Next
End If
Next
ElseIf TypeOf lsc Is IMap Then
Dim c As IMap = lsc
For i = 0 To c.LayerCount - 1
If TypeOf c.Layer(i) Is IFeatureLayer Then
pFeatureLayer = c.Layer(i)
For Each s In LayerSources(pFeatureLayer)
cOutput.Add(s)
Next
ElseIf TypeOf c.Layer(i) Is IRasterLayer Then
pRasterLayer = c.Layer(i)
For Each s In RasterSources(pRasterLayer)
cOutput.Add(s)
Next
For Each s In InfoDirContents(pRasterLayer.FilePath())
cOutput.Add(s)
Next
ElseIf TypeOf c.Layer(i) Is ICompositeLayer Then
pCompositeLayer = c.Layer(i)
For Each s In ProcessLayerSourceCollection(pCompositeLayer)
cOutput.Add(s)
Next
End If
Next
Else
Debug.WriteLine("Unknown type of lsc.")
End If
Return cOutput
End Function
It’s still slightly better than the original, because it displays the explicit parallels between the two parts. That will make it a little easier to maintain. A slightly better factoring would have been to make separate functions to process each type.