Working With Control Files II

by Nik Johnson

Published 1999-08-10    Printer-friendly version

I'm an inveterate template tinkerer. It's a mixed blessing. By the time I abandoned my CPD 2.1 model file for the Logix Project Manager, there was more logic devoted to branching conditionally around various options than to perform the actual task at hand. By the time CDD 3007 rolled around, I had so much invested in modifying Todd Carlson's templates that I didn't dare switch to newer Clarion templates. But while they lasted these modified tools made me a lot more productive than I would otherwise have been.

I'm still tinkering, but the introduction of OOP and the ABC library has meant that I can now work within the TopSpeed framework rather than around it. Steve Parker's article on control files in the previous issue provides an opportune way to illustrate the convenience and power of OOP/ABC while at the same time extending my toolkit.

Defining The Problem

What I want to build is a "set and forget" method of handling control files. As Steve points out, some file structures require a SET/NEXT approach, others do better with GET/POINTER. Still others may require other access methods. I want to spend an absolute minimum of time and effort incorporating control files into future applications, independent of the file system.

Steve has enumerated the things our tools need to be able to do:

  • Open the file
  • If the file is missing, optionally create it
  • Read the file's only record in a way appropriate to the file structure
  • If the file has no records, add one
  • Update the record
  • Close the file

The following narrative mimics the way I go about building tools for my own use. First, list the things that have to be done; second, establish a general design; third, build a skeleton; fourth, fill in the details. This works for a small shop, but if you're building tools for sale or working in a larger organization you may want to skew this pattern toward heavier documentation of the design in advance of coding.

This Wheel Has Already Been Invented ... Almost

The ABC FileManager class provides facilities to accomplish five of these six tasks, so it makes sense to start with that as a basis. A class based on FileManager can inherit all of its functionality and minimize the new work that needs to be done.

MyFileManager CLASS(FileManager)
Fetch           PROCEDURE,BYTE,PROC
              END     

The new method provides a place to add Steve's access logic, but it doesn't include provision for specifying which version of that logic to use. ABC classes are insensitive to driver, but in this case that luxury is not available.

Two possibilities come to mind. First, the fetch algorithm could accept a flag to designate how access to the one and only record in the file is to be gained. A byte should be sufficient, since more than 255 variations on the get-only-record theme are unlikely. However, if the file driver changes, it could be inconvenient to chase down every Fetch and alter it. The second approach, adding a property to the derived file manager, permits the fetch algorithm to be specified once at the beginning of the program.

MyFileManager CLASS(FileManager)
FetchOnlyType   BYTE
Fetch           PROCEDURE,BYTE,PROC
              END  

The compiler can distinguish between the new Fetch method and the one specified in the standard ABC FileManager class because their prototypes can be differentiated by the rules for procedure overloading. (See pp. 89-90 in the Language Reference Manual.)

Building A Skeleton

A residence for record fetching logic having been established, the logic itself can be added.

First, set up an EQUATE for each value MyFileManger.FetchOnlyType can assume:

            ITEMIZE,PRE(FetchMethod)
GetByPosition EQUATE
GetNext       EQUATE
            END

Although these are the only two options at the moment, setting up this structure provides a clean way to add other options in the future.

The new Fetch method, by using the EQUATEs, becomes self-documenting.

MyFileManager.Fetch PROCEDURE
ReturnValue BYTE,AUTO
  CODE
  ASSERT(SELF.FetchOnlyType) 
  SELF.Open
  SELF.UseFile
  CASE SELF.FetchOnlyType
  OF FetchMethod:GetByPosition
    ! "get by position" code here ...
  OF FetchMethod:GetNext
    ! "get by set/next" code here ... 
  END
  IF ReturnValue
    CLEAR(SELF.File)
    ReturnValue = SELF.Insert()
  END
  SELF.Close
  RETURN ReturnValue

This "shell" contains everything except the actual logic to access the configuration record if it exists. In adapting the logic from Steve's article, I've made a few assumptions based on my own expected use of the method:

  • I may want to access the configuration record in a context other than an update form, so I've removed logic that refers to "SetupForm" from Steve's code.
  • I should never call this method unless I've set the FetchOnlyType property. The ASSERT protects against this. Since this would be a programming error as opposed to a data-related condition, I don't need user-friendly error messages for this situation.
  • If I ask for a record and can't find one, I always want to add the record.

Your programming style may suggest a different set of design assumptions.

Putting Meat On The Bones

The only thing left to do is add appropriate code for accessing the configuration record. Whatever the method used, ReturnValue should be set to zero if the fetch is successful, something else if not. The CASE structure is the only part of the shell which changes:

CASE SELF.FetchOnlyType
  OF FetchMethod:GetByPosition
    GET(SELF.File,1)
    IF ERRORCODE()
      ReturnValue = Level:Notify
    ELSE  
      ReturnValue = Level:Benign
    END
  OF FetchMethod:GetNext
    SET(SELF.File)
    ReturnValue = SELF.Next()
END

The new method behaves very much like the standard ABC Fetch except that it doesn't require specification of a key, it expects one and only one record in the file, and, if the file is empty, it adds a cleared record.

Adding The New Class To The ABC Library

A little plagiarism is a wonderful thing. All the information needed to make making the new class look to Clarion like an ABC class is sitting in the \LIBSRC directory. First, set up files for the new class prototypes and methods. Call them something like MyClasses.inc and MyClasses.clw. Referring to similar files shipped with Clarion, set up these two files to match their style.

Here's the general setup of MyClasses.inc:

!ABCIncludeFile
 OMIT('__EndOfInclude__',_MyClassesPresent_)
_MyClassesPresent_ EQUATE(1)
    INCLUDE('ABFILE.INC'),ONCE
 ! class prototypes here ...
__EndOfInclude__

The comment (!ABCIncludeFile) tells the IDE that this code follows the ABC pattern and should be treated as any other ABC include file. The OMIT structure lets this file be included anywhere the definitions are needed without fear of duplicating those definitions. For example, the definitions of the ABC FileManager are included above so that they can be used in the definition of the MyFileManager class.

The new ONCE attribute provides another mechanism for avoiding duplicate definitions, but that protection depends on the ONCE attribute appearing in every INCLUDE. Older code may still require the protection provided by the OMIT structure, so it's a good idea to leave it in place.

A little more creative plagiarism provides the general setup of MyClasses.clw:

MEMBER
_ABCDllMode_  EQUATE(0)
_ABCLinkMode_ EQUATE(1)
  MAP
  END
  INCLUDE('MyClasses.inc')

The MEMBER statement identifies this as a source module, something the compiler needs to know. The MAP structure is required, since it causes the compiler to include prototypes for Clarion language statements and functions. The INCLUDE of MyClasses.inc makes the new class definitions available to code in this module and, if the nest is not too deep (LRM page 95), also includes definitions from ABFILE.INC.

The two equates, for _ABCDllMode_ and _ABCLinkMode_, implement the ABC library's method of determining where and how these methods will be linked and referenced. These definitions work with attributes in each CLASS statement which are required and will be added next.

In the skeleton version of the CLASS prototype, some necessary attributes were ignored. The full statement should have been:

MyFileManager CLASS(FileManager),|
                    TYPE ,  |
                    MODULE('MyClasses.clw'), |
                    LINK('MyClasses.clw',_ABCLinkMode_), |
                    DLL(_ABCDllMode)

The TYPE attribute identifies this class as a prototype only. Actual instances of the class will be created (instantiated) when needed. The MODULE attribute tells the compiler where to find the code for the class methods. The LINK attribute tells the compiler to compile and link these methods if, and only if, _ABCLinkMode_ is True. The DLL attribute tells the compiler to look for these methods in another DLL if, and only if, _ABCDllMode_ is True.

The full code for MyClasses.inc and MyClasses.clw is available for download at the end of this article.

Have Hammer, Need Nail

Having built a new class, the next challenge is to use it. There are at least two likely situations:

  • Access within the context of some other process
  • Access in conjunction with an update form

The first type of use is easy. Just use the Fetch method as you would the standard ABC Fetch:

IF NOT Access:MyConfigFile.Fetch
  ! do something, possibly including ...
  Access:MyConfigFile.Update
END

(This code assumes that nothing goes wrong during the update. You are of course free to be as conservative as the situation warrants.)

The second is even easier. In the ThisWindow.Init method of a form, before the code stores GlobalRequest, insert:

IF Access:MyConfigFile.Fetch
  RETURN Level:Fatal
ELSE
  GlobalRequest = UpdateRecord
END

This allows the form to be called directly from a menu and, no matter how it is called, causes it to update the one and only record in our configuration file.

To make this capability available for a particular file, set Access:MyConfigFile.FetchOnlyType to a value indicating how the single record should be accessed. This can be done anytime after the FileManager instances are initialized and before the Fetch is called.

Building A Wrapper

It would be nice if all of the necessary housekeeping could be wrapped up in a simple package so that implementing a configuration file would require nothing more than, say, adding a word to the user options of that file in the dictionary. Making things that simple is probably overkill, though, since most projects will have only one file of this type.

An application level extension template can do the same thing cleanly and without the extra processing needed to check every file in the dictionary for a user option. Given the name of a file, the template can initialize the corresponding file manager's FetchOnlyType property and add setup code to any form procedure which uses the file.

This is as good a time as any to set up a file for home grown templates. Call it MyTemps.tpl. A single line identifies the template chain:

#TEMPLATE(MyTemps,'Homegrown Templates'),FAMILY('ABC')

Another line establishes the extension template:

#EXTENSION(ConfigFile,'Implement Configuration File'),APPLICATION,MULTI

The APPLICATION attribute tells the generator that this extension is applied at the application level rather than the procedure level. The MULTI attribute permits more than one instance of the extension in a single application.

A couple of lines of documentation describing what this template is supposed to do are in order:

#DISPLAY('This extension adds code to handle a specified file') 
#DISPLAY('as a configuration file containing one and only one')
#DISPLAY('record. Forms for which this file is the primary file')
#DISPLAY('can be called directly without an intervening browse.')
#DISPLAY(' ')

A prompt allows specification of the file:

#PROMPT('Configuration File:',FILE),%MyConfigFile,REQ

There are at least two ways to specify the access method. The easiest is to add a second prompt (or more accurately, prompt structure):

#PROMPT('Choose Access Method:',OPTION),%MyAccessMethod
#PROMPT('Get by position',RADIO)
#PROMPT('Get next',RADIO)

Another alternative is to have the template select the access method based on driver. This has the advantage of hiding the access method from the programmer, thereby reducing the number of things that have to be remembered when using the template. A good place to put this selection logic is in an #ATSTART section:

#ATSTART
  #FIX(%File,%MyConfigFile)
  #CASE(UPPER(%FileDriver))
  #OF('CLARION')
  #OROF('DBASE3')
  #OROF('DBASE4')
    #SET(%MyAccessMethod,'Get by position')
  #OF('TOPSPEED')
    #SET(%MyAccessMethod,'Get next')
  #ELSE
    #ERROR('File driver not recognized by ConfigFile extension')
  #ENDCASE 
#ENDAT

At this point everything necessary to generate code is in place. First, provide for initialization of Access:MyConfigFile.FetchOnlyType:

#AT(%ProgramSetup)
  #CASE(%MyAccessMethod)
  #OF('Get by position')
Access:%MyConfigFile.FetchOnlyType = FetchMethod:GetByPosition
  #OF('Get next')
Access:%MyConfigFile.FetchOnlyType = FetchMethod:GetNext
  #ENDCASE
#ENDAT

Adding code to the input form is a little trickier. The basic structure is easy:

#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),PRIORITY(0)
IF Access:MyConfigFile.Fetch
  RETURN Level:Fatal
ELSE
  GlobalRequest = UpdateRecord
END
#ENDAT

Specifying PRIORITY(0) puts the code at the very beginning of the method, which is necessary because GlobalRequest is internalized very early in the initialization process.

This code should only be generated if the window is an update form for MyConfigFile. The template language provides a WHERE attribute to allow this kind of selection. With that attribute in place, the #AT statement becomes:

#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),    %|
                     PRIORITY(0),                         %|
                     WHERE(%ProcedureCategory = 'Form'    %|
                            AND %Primary = %MyConfigFile)

One of the benefits of writing articles is that you learn things in the process. Until I had to split a template instruction to fit the printed page I was unaware that the template language has a line continuation symbol and that it differs slightly from the continuation symbol used in Clarion code. You'll find it documented on page 503 of the Programmer's Guide.

Unfortunately, when I tried to use this syntax in an example, the registry rejected the template. You'll find a note which suggests that this might happen on page 525 of the Programmer's Guide. So the continuation symbols (%|) in the above example are there for readablity only and should not be used in actual code.

The point here is not that the documentation is bad. In fact, it's very good. But the template language has always had more little vagaries and nuances than the Clarion language itself, which makes documentation a daunting task. With both template and Clarion code, I read the documentation, code accordingly, test, and modify until it works. But with templates I don't worry too much if I can't explain in detail why what works, works.

Testing also reveals that %Primary, a built-in symbol which the documentation suggests should contain the label of the procedure's primary file, is blank when the WHERE clause is evaluated. Why? I don't know. But replacing %Primary with %File, a multi-valued built-in symbol, works. Why? I don't know.

Applying The Tool

The three files which define the MyFileManager class and the ConfigFile extension are included in a downloadable ZIP file. Using them is very simple:

  • Place MyClasses.inc and MycClasses.clw in your Clarion LIBSRC subdirectory.
  • Place MyTemps.tpl in your Clarion TEMPLATE subdirectory
  • Register the ConfigFile extension.
  • For any file that you want to handle as a configuration file, add an instance of the extension to your application's GLOBAL properties.

You will also need to tell the generator to use MyFileManager instead of FileManager for any configuration files. You can do this either on the Individual File Overrides tab or the Classes tab. The difference will be whether all files use MyFileManager (Classes tab) or just the ones you want handled as configuration files (Individual File Overrides tab).

I usually choose the more general case, expecting to add other functionality to the derived file manager.

In summary, once you have found a solution to a particular coding problem, it makes sense to take the extra step and build tools which implement that solution. Clarion's tool-building facilities are within the abilities of the average programmer, and skill in using those facilities improves rapidly with practice. I hope this example not only proves useful to you, but tempts you to build other tools on your own. You have nothing to lose and a world of productivity to gain.

A Correction

After this article first appeared, I received an email from Jan Jacob de Maa. He had downloaded the associated ZIP file and was trying to use it. Unfortunately, he was encountering compile errors.

Jan Jacob's experience led to the discovery of three errors in the article, which errors are repeated in the ZIP file:

  • In the header for the MyFileManager class, an underscore is missing in the DLL attribute. It should read DLL(_ABCDllMode_) rather than DLL(_ABCDllMode). The missing underscore causes the DLL attribute to remain off when it should be on, wreaking havoc when compiling and running in 32-bit mode.
  • In the wrapper template, the line IF Access:MyConfigFile.Fetch is missing the parentheses necessary to tell the compiler that this is a function rather than a variable. It should read IF Access:MyConfigFile.Fetch().
  • Also in the wrapper, the priority of zero which was given for code to be inserted in ThisWindow.Init causes the wrapper to execute its Fetch operation before the file is open. Changing the priority to 8000 places the code correctly. Also, because at this new position GlobalRequest has already been internalized, GlobalRequest = ChangeRecord has been changed to SELF.Request = ChangeRecord.

Download the updated source code


Nik Johnson stumbled into the programming racket in 1959 when his boss at Grumman Aircraft insisted on his attending a Fortran class. Since 1986 he has been using Clarion to help clients solve information handling problems.

Printer-friendly version

Reader Comments

Posted on Sunday, March 20, 2005 by Murray Oldfield

This did not work with Clarion 6.1
Have posted to newsgroup, will update here if I get anm answer
MO

 

Posted on Monday, March 21, 2005 by Nik Johnson

I suspect that it didn't work in earlier versions either.

The lines

_ABCDllMode_  EQUATE(0)
_ABCLinkMode_ EQUATE(1)

are certainly version dependent. For Clarion 6.1 you'll need to remove these.

The other issue is the assert:

ASSERT(INRANGE(SELF.FetchOnlyType,1,2))

This is placed to assure that the fetch type is set before the class attempts to use it. The problem is in the definitions of fetch types:

            ITEMIZE,PRE(FetchMethod)
GetByPosition EQUATE
GetNext       EQUATE
            END

This structure produces either 0 or 1, while the ASSERT checks for either 1 or 2.

Correct the situation by changing the definition to:

            ITEMIZE,PRE(FetchMethod)
GetByPosition EQUATE(1)
GetNext       EQUATE
            END
 
Hope this helps.

-Nik

 

Posted on Monday, March 21, 2005 by Nik Johnson

I suspect that it didn't work in earlier versions either.

The lines

_ABCDllMode_  EQUATE(0)
_ABCLinkMode_ EQUATE(1)

are certainly version dependent. For Clarion 6.1 you'll need to remove these.

The other issue is the assert:

ASSERT(INRANGE(SELF.FetchOnlyType,1,2))

This is placed to assure that the fetch type is set before the class attempts to use it. The problem is in the definitions of fetch types:

            ITEMIZE,PRE(FetchMethod)
GetByPosition EQUATE
GetNext       EQUATE
            END

This structure produces either 0 or 1, while the ASSERT checks for either 1 or 2.

Correct the situation by changing the definition to:

            ITEMIZE,PRE(FetchMethod)
GetByPosition EQUATE(1)
GetNext       EQUATE
            END
 
Hope this helps.

-Nik

To add a comment to this article you must log in.

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $189

(includes all back issues since '99)

Renewals from $139

Two years: $289

Renewals from $239

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links