Friday, May 30, 2008

Stacking Stuff

A stack is a data structure that's been around the computing world since the beginning, and is part of every CPU. Recently I ran into a problem where a stack was the perfect solution.

The issue was saving and restoring the state of global settings, in this case ON('ESCAPE') and SET('ESCAPE'). At the beginning of a routine where I change them (the RunReport method), I stored the current values into properties of the object (I used properties rather than local variables because another method does the actual restore), changed these settings as needed, and at the end of the routine called a method that restores the settings from the saved values. (I'm sure you've written code like this a hundred times.)

However, here's the problem I ran into:

  • RunReport calls a method of another object (PrintReport) that also saves, changes, and restores these settings. This wouldn't normally be a problem because PrintReport cleans up after itself before it exits. However ...
  • We added drilldown capabilities in Stonefield Query. While a report is being displayed, execution is still in PrintReport. If you click a link in the report, RunReport is called again to run the linked report.

See the problem? In the second call to RunReport, the settings it saves are those set by PrintReport, which overwrite the original settings it saved. So, by the time you close the reports, the code restoring the settings doesn't set them to the original values.

Since you can have a virtually unlimited number of levels of drilldowns, it made sense to implement a stack to store the settings. At the start of a routine, the code pushes the current settings onto the stack and at the end, pops them off.

As a refresher for stacks, think about a stack of plates in one of those spring-loaded plate holders you see in some restaurants. When a new plate is added to the stack, it's added at the top and the rest of the plates are pushed down. When a plate is removed, it's the one at the top of the stack (the one added most recently) and the rest of the plates move up. That's exactly what we want to happen in this case. Adding an item to a stack is called "pushing" and removing an item is called "popping".

I briefly considered using an array to hold the settings, but decided instead to use one of my favorite classes, Collection. The implementation was unbelievably simple. I subclassed Collection and added two methods: Push and Pop. Here's the code for Push:

lparameters tuValue
This.Add(tuValue)

Here's the Pop method:

local luValue
with This
    if .Count > 0
        luValue = .Item(.Count)
        .Remove(.Count)
    else
        luValue = .NULL.
    endif This.Count > 0
endwith
return luValue

Using Count as the index for the item to pop and remove ensures the last pushed item is the one that's popped. Note that I decided to return NULL if the stack is popped more often than pushed (that is, Count is 0). You might decide to throw an error instead.

Here's how this class is used:

loStack = newobject('SFStack', 'SFStack.vcx')
loStack.Push(ON('ESCAPE'))
loStack.Push(SET('ESCAPE'))
* change the settings and do whatever
lcCurrEscape   = loStack.Pop()
lcCurrOnEscape = loStack.Pop()
on escape &lcCurrOnEscape
if lcCurrEscape = 'OFF'
    set escape off
endif This.cCurrEscape = 'OFF'

Note that items are removed from the stack in the opposite order they're added, so this code handles that when it retrieves the saved settings.

2 comments:

Anonymous said...

As great as VFP is, it is amazing that after all this time it doesn't have some basic data structures natively in the code, such as a stack or a hash table.

It also bothers me that VFP doesn't have such a basic OOP construct of an interface.

But, I guess the good thing is it is just that easy to create some of this stuff.

Mike said...

Hi Doug,

Great idea! If you used an object to save and restore your escape settings (storing the original setting in the Init(), and restoring the settings in the Destroy()), this would be even easier to implement. Just push the object on the stack, and pop it off when you're done. Then you don't have to remember what order you pushed each item onto the stack.

Mike