OpenQM Object Primer - The 'Lst' Class

This is a very simple primer for a QM Class called "Lst". This class is a very simple set of routines designed to manage related lists of data in BASIC. The goal of the Lst Class is to conveniently store and retrieve sets of data keyed by a name. Each set can contain an associated value, plus an optional list of tag=tagvalue elements. This class was designed to facilitate inter-process messaging and connectivity to JMS (Java Messaging Service), but is a useful storage class in itself.

The 'Lst' Class is nothing special. It is not built into QM. It is just a collection of application code like any other cataloged application subroutine or function.

In non-OO code, you could implement a sub-set of the Lst Class functionality with a subroutine that looked something like:

SUBROUTINE LstManager(cmd, name, val)
     
     COMMON /LstManager/ Names, Values
     
     begin case
        case cmd = 'init'
           Names = ''
           Values = ''
        case cmd = 'set'
locate(name,Names;l) else l = dcount(l,@am)+1 Names<l> = name
Values<l> = val
case cmd = 'get'
locate(name,names;l) then
val = Values<l>
end else
val = '' end case cmd = 'delete' locate(name,Names;l) then Names = delete(Names,l,0,0) Values = delete(Values,l,0,0) end case cmd = 'list' val = Names end case return

This is a very simple subroutine that will let you keep a list of values associated with names. Because of how this is written, there are some limitations that your application needs to be concerned with:

This is a really useful subroutine, and using it is fairly straight-forward.

     CALL LstManager('init','','')
     CALL LstManager('add','Name','George')
     CALL LstManager('add','Addr','100 Main Street')
     ...
     CALL LstManager('list','',lst)
     for i = 1 to dcount(lst,@am)
        CALL LstManager('get',lst<i>,val)
        display lst<i> : ' -> ' : val
     next i

While this is easy enough to use, the syntax is still somewhat verbose. So, what would the calling program look like with objects:

     lst = object('Lst')
     lst->val('Name') = 'George'
     lst->val('Addr') = '100 Main Street'
     ...
     lst = lst->lst
     for i = 1 to dcount(lst,@am) 
        display lst<i> : ' -> ' : lst->val(lst<i>)
     next i

The OO version of this function has a number of characteristics:

If the 'Lst Class' is written well, the limitations of the subroutine are also mitigated.

Of course, the subroutine could get these features as well. Making the subroutine case insensitive is pretty trivial. Setting up the subroutine to allow for binary storage is somewhat harder. Allowing for multiple lists, and even lists of lists, gets a lot harder. With objects you can even do:

     ...
     lst2 = object('Lst')
     lst2->val('Item1') = '00001'
     lst2->val('Item2') = '00002'
     lst->val('SubList') = lst2

So now lists can contain lists. Lists can actually contain any data that fits in a variable, so this includes strings, objects, and even things like file handles. But what does it take to make the Lst Class actually work.

'Lst' Class formal property/method list:

Classes are usually documented "from the outside". It is important that applications know how to use a class. How the class actually works internally is generally more nebulous. A well designed set of public properties and methods for a class will hide the inner working of the class allowing for modifications to the class's inner workings without requiring changes to the calling programs.

Init
obj->Init
Method Initialize the list to empty
val
obj->val(name) = value
result = obj->val(name)
Read/Write Property to reference a list element
delete obj->delete(name) Method Method to remove a name from the list
lst
result = obj->lst
Read only Retrieves a list of all names as a dynamic array
tag
obj->tag(name,tagname) = value
result = obj->tag(name,tagname)
Read/Write Property to reference a tag value for a particular name
DelTag
obj->DelTag(name,tagname)
Method Method to delete a tag from a name element
TagLst
result = obj->TagLst(name)
Read only Retrieve a list of all tag names for a particular name
str
obj->str = value
result = obj->str
Read/Write Property that converts the entire array to and from a single packed string.
copy
obj->copy(obj2)
Method Method to copy one list collection into another.

In QM, Classes are defined in a QM/Basic source code item in the same way that you would write a subroutine. In fact, the similarities between Classes and subroutines extends down to the run-time Basic level itself. Classes are compiled and cataloged just like subroutines. They have names just like subroutines, and their names must be unique, not only against other Classes, but against other cataloged programs and subroutines as well.

'Lst' Class source code:

Class Lst
All Classes start with a "CLASS" directive, just like a subroutine or function.
$catalog local
Classes have to be cataloged. In this case, we decide to catalog this class 'locally'. You could also catalog this class as a 'regular' catalog item, or 'globally'.
* PUBLIC PublicVariable

This is where you can define public variables. This particular class definitions does not have any public variables.

Public variables can be used directly from any program that has an object variable.

obj->PublicVariable = 'Hello'
display obj->PublicVariable
PRIVATE names
PRIVATE tags
PRIVATE vals

These are private variables. They will persist for as long as the object exists. If you create several instances of the same class:

obj1 = object('Lst')
obj2 = object('Lst')

then each object "instance" will get it's own private variables.

Private variables cannot be used outside of the class but are available to all code inside of the class.

PUBLIC SUBROUTINE Init
   names = ''
   tags  = ''
   vals = object('Array')
end

We want a routine that will erase all data. This would be called as:

obj->Init

Note that 'vals' is actually an Array object.

PUBLIC SUBROUTINE CREATE.OBJECT
   me->Init
end
The behaviour of PUBLIC and PRIVATE variables is to initialize as zeros. In this case we want them to initialize with nulls. We just setup the CREATE.OBJECT built-in subroutine to just call our own Init routine.
PUBLIC SUBROUTINE DESTROY.OBJECT
end
There is also a built-in subroutine that is called when the object is destroyed. We don't need this with Lst, so we can put in a blank routine, or just leave it out all together.
GET Val(name)
   if name = '' then return('')

   locate(downcase(name),Names;l) then
return(vals->val(l+2))
end else return('')
end
end

This is code that retrieves a named value. We store names in a dynamic array and use a simple locate statement to look them up. Values are stored in a dimensioned array so there are no limits to the type of data that you can store. The dimensioned array will also be much faster if the collection gets large.

display obj->Val('name')

The value itself is stored in the array object in position l+2. The offset of 2 allows us to use the Array->str property to convert a list to a single string with minimal overhead.

SET Val(name,val)
   if name = '' then return
   locate(downcase(name),mames;l) then
vals->val(l+2) = val end else locate('',names;l) else l = dcount(names,@am) + 1 end names<l> = downcase(name) tags<l> = '' vals->val(l+2) = val end end

This is code that sets a value. The values themselves are stored in a dimensioned array and this dimensioned array may need to be extended as it's size grows. We extend it in blocks of 20 just to help performance out a little bit.

If we wanted this object to perform well even with thousands (or millions) of names and values, we could create and open a temporary file and store the data there. This would limit the class to storing only string data, but would make it useful even for very large amounts of data.

obj->Val('name') = 'Some data'
PUBLIC SUBROUTINE Delete(name)
   locate(downcase(name),Names;l) then
      Names<l> = ''
      tags<l>  = ''
      vals->val(l+2) = ''
   end
end

This is code that will delete a name/value pair. We don't compress the arrays, but just leave blank holes. The 'SET Val' routine is designed to re-use these holes, so compressing the value list would not help much anyway.

obj->Delete('name')
PUBLIC FUNCTION Lst
   res = Names
   loop
      locate('',res;l) then
         res = delete(res,l,0,0)
      end else
         exit
      end
   repeat
   return(res)
end

This is code that will retrieve a list of names as a dynamic array. In most cases this will be a simple assignment, but deleted names can leave holes that need to be removed.

lst = obj->Lst
for i = 1 to dcount(lst,@am)
   display lst<i> : '=' : obj->Val(lst<i>)
next i
GET Tag(name,tag)
   if name = '' then return('')
   if tag  = '' then return('')
   locate(downcase(name),names;l) then
      locate(downcase(tag),tags,1,l;ll) then
         return(tags<l,2,ll>)
      end
   end
   return('')
end

This is code that will retrieve a tag value associated with a particular list element name

display obj->tag('name','tp')

Note that in this implementation, tag values must be simple strings and cannot contain system delimiters.

SET Tag(name,tag,val)
   if name = '' then return('')
   if tag  = '' then return('')
   if val  = '' then return('')
   locate(downcase(name),names;l) then
      locate(downcase(tag),tags,1,l;ll) then
         tags<l,2,ll> = val
      end else
         tags<l,1,-1> = tag
         tags<l,2,-1> = val
   end
end

This is code that will set a tag value associated with a particular list element name

obj->tag('name','tp') = 'text'
GET TagLst(name)
   if name = '' then return('')
   locate(downcase(name,names;l) then
      return(change(tags<l,1>,@svm,@am)
   end
   return('')
end
You can also retrieve a list of all tag names associated with a list element.
GET str
   vals->val(1) = names
   vals->val(2) = tags
   return(vals->str)
end

The 'str' property provides a way to take a collection of names and values and save them to a single string. This does have some limitations in that it assumes that the values are all strings and don't contain things like file handles or object handles. The strings are binary clean though, so you can have collections of entire database records and even host binary records like images or application data files.

Uses for this string are up to the application. You might write it to a file for another process to read, transmit it over a network connection, use it as a calling parameter to a subroutine, or whatever.

Because the values are already stored in an Array object, where we have left the first two elements open, we can easily use the Array Object's str property directly.

write obj->str on temp.file , 'temp.id'
SET str(str)
   vals->str = str
   names = vals->val(1)
   tags  = vals->val(2)
end

This SET property lets you restore a state that was previously saved.

read str from temp.file , 'temp.id' then
   obj->str = str
end
PUBLIC SUBROUTINE copy(lst2)
   me->Init
   names2 = lst2->lst
   for i = 1 to dcount(names2,@am)
      name = names2<i>
      me->val(name) = lst2->val(name)
      tags2 = lst2->TagLst(name)
      for j = 1 to dcount(tags2,@am)
         tag = tags2<j>
         me->tag(name,tag) = lst2->tag(name,tag)
      next j
   next i
end
And just for convenience, we will provide a copy method so that you can duplicate one list object into another.
end
... and all programs end with an end