Chapter 15: Collection Frameworks

This chapter introduces the frameworks used by the collection classes in the Base Class Library. Collection frameworks are mainly concerned with the communication between collections and the elements they store.

All the code shown in this chapter uses the Micro Focus alternatives to the ISO 2002 syntax.

Overview

The COBOL collection classes assume that their elements are objects. A collection sends messages to its elements to get information such as the element's size and content. Even when you store intrinsic data (as opposed to objects), the collection uses the methods implemented in the intrinsic classes and uses the INVOKE ...AS verb to send messages to the data.

The elements stored in a collection need to include in their interface the methods to respond to those messages. Default versions of most of these are provided in the Base class, but you may want to override these to provide different or more efficient behavior for particular types of object.

You will also need to provide methods which conform to a particular interface for the collection class sort and iterator methods.

Different Categories of Collection

The different types of collection in the Base Class Library can all be categorized by the following properties:

Here are some of the collection classes available, with their properties:

Bag Non-indexed, automatically growable, duplicates allowed
Array Indexed, manually growable, duplicates allowed
CharacterArray Indexed, manually growable, duplicates allowed
OrderedCollection Indexed by insertion order, automatically growable, duplicates allowed
SortedCollection Indexed by sort order, automatically growable, duplicates allowed
ValueSet Non-indexed, automatically growable, duplicate values disallowed
IdentitySet Non-indexed, automatically growable, duplicate object handles disallowed
Dictionary Indexed by key, automatically growable, duplicate key values disallowed
IdentityDictionary Indexed by key, automatically growable, duplicate key object handles disallowed

ValueSet and IdentitySet differ only in the way they determine duplicate elements. ValueSets compare the values of elements and disallows duplicate values; IdentitySets compare object references, and disallow storing the same object more than once.

Dictionary and IdentityDictionary are special types of collection which store key/data pairs. They determine duplicate keys in the same way that ValueSet and IdentitySet determine duplicate elements.

Creating Collections

You can create a collection to store either objects (a collection of references) or intrinsic data (a collection of values). A collection of references can contain more than one sort of object, whereas a collection of values is initialized for storing data of a particular type and length.

Before you create a collection, you need to decide which type to create. You need to decide:

The available types of collection cover most of the combinations above.

To create a collection of references, send the "ofReferences" message to the class of collection you want to create passing the initial size and template as parameters.

To create a collection of values:

  1. Clone a template for the intrinsic data from one of the intrinsic data classes; see the section the section Cloning an Intrinsic Data Class in the chapter Intrinsic Data.

  2. Send the "ofValues" message to the class of collection you want to create passing the initial size and template as parameters.

Many of the collection classes have additional create methods. For further information see the Class Library Reference.

Creating Dictionaries

Dictionaries are a special sort of indexed collection, which store key-data pairs (known as associations). In a dictionary, the key is used as the index when you store or retrieve the data. Dictionaries do not allow you to store duplicate keys.

Like the other collection types, you can store either objects or intrinsic data in a dictionary. In a dictionary though, either the key or the data part can be an intrinsic or an object. This gives you the following possible combinations for intrinsic or object storage:

If the keys are objects, they must provide methods for hashing and equality comparisons. Objects like CharacterArray already provide these methods, but if you are using your own types of objects as keys, you need to provide your own mechanisms.

When you create a dictionary you have to give it a template so that it knows how the key and data portions are to be stored. The template is either the Association class, or a clone of the Association class.

The Association class is another clonable class, like the classes for intrinsic data classes, used for creating templates for data storage. An Association template actually consists of two templates; one for the key and one for the data. To create any type of dictionary you need to create an Association template.

Having created a template for your dictionary object, there are two methods you can use to create a dictionary itself; "ofValues" or "ofAssociations". A dictionary "ofValues" stores each element as a key data pair. A dictionary "ofAssociations" stores each element as an instance of the Association template you used to create the dictionary.

A dictionary "ofAssociations" in effect stores three items for every entry in the dictionary:

A dictionary "ofValues" stores the key and data directly without wrapping them inside an association object. When would you use a dictionary "ofAssociations" and when would you use a dictionary "ofValues"?

A dictionary "ofValues" is slightly more efficient at run time in terms of speed and memory if you simply want to store data items against key items. However, if your application uses associations elsewhere to manage key/data pairs, a dictionary "ofAssociations" is a better choice as the actual dictionary only stores the object handles to the associations, and you don't have to extract the key and value from the association before putting it in the dictionary.

The following example shows the creation of a dictionary with an intrinsic key and data, and the creation of the templates.

00001  local-storage section.
00002  01  aKeyTemplate      object reference.
00003  01  aDataTemplate     object reference. 
00004  01  anAssocTemplate   object reference.
00005  01  aDictionary       object reference. 
00006  01  aLength           pic x(4) comp-5.
 
    ...

00020  procedure division.
        ...
00028      move 3 to aLength
00029      invoke CobolCompX "newClass" using aLength
00030                               returning aKeyTemplate
00031      move 20 to aLength
00032      invoke CobolPicX "newClass" using aLength 
00033                             returning aDataTemplate 
00034      invoke Association "newClass using aKeyTemplate
00035                                         aDataTemplate
00036                               returning anAssocTemplate
00037      invoke Dictionary "ofValues" using anAssocTemplate
00038                                         aLength
00039                               returning aDictionary

Lines 1-6 Declares storage for the templates..
Lines 28-29 Creates a template for a PIC X(3) COMP-X numeric key.
Line 31-32 Creates a template for a PIC X(20) data portion.
Line 34 Creates an association template.
Line 37 Creates a dictionary of values with an initial size of 20 elements. This will grow to accommodate extra elements once 20 elements have been added. Each grow operation approximately doubles the size of the dictionary.

Working with CharacterArray Objects

A CharacterArray is an object that contains a string of any length. CharacterArrays are a convenient way of passing strings as parameters when sending messages or making calls.

Creating a CharacterArray Object

There are many ways to create a CharacterArray. The two most convenient are listed below.

where:

zString Any null-terminated string
aCharArray Object reference
aLiteral Any COBOL literal, or data item containing one.
aLength PIC X(4) COMP-5. Length in characters of aLiteral.

Querying a CharacterArray Object

There are many ways to get data from a CharacterArray object. Three are given below:

where:

aCharArray Object reference; contains the handle to the CharacterArray object
aLength PIC X(4) COMP-5. Length in characters of the CharacterArray object
aValue PIC X(n) Value of the CharacterArray object. It is your responsibility to see that aValue is long enough to hold the result.
The "asParameter" method returns a string one byte longer than the result from the "getValue" method, to allow for the null at the end.

Comparison Between Elements

Collections carry out two types of comparison on elements:

Equality

A collection compares two elements by sending the "equal" message to one element, giving it the other element as a parameter. The default "equal" method in Base simply compares the object references of the two objects (that is, they are equal only if they are the same object).

For many applications you will want to compare part or all of the element's instance data to determine equality. Implement the interface to "equal" as follows:

 method-id. "equal".
 linkage section.
 01 lnkElement                object reference. 
 01 lnkEqualityResult         pic x comp-5.
  88 isEqual                  value 1. 
  88 isNotEqual               value 0. 
 procedure division using lnkElement
                    returning lnkEqualityResult.
* Code to compare lnkElement to self and return 
* lnkEqualityResult.
    exit method.
 end method "equal". 

Relative Value of Objects

The default sort method of a SortedCollection instance sends the "lessThanOrEqual" message (if it is a collection "ofReferences") or the "lessThanOrEqualByLengthValue" message (if it is a collection "ofValues") to all the elements in the collection. There is no default method for this in Base, although it is implemented by the Intrinsic classes and by CharacterArray.

You may need to implement this method so that two objects can compare themselves using part of their instance data. Use the following interface to implement the "lessThanOrEqual" method:

 method-id. "lessThanOrEqual".
 ...
 linkage section.
 01 lnkString             object reference.
 01 lnkResult             pic x comp-5.
  88 isEqual              value 1.
  88 isNotEqual           value 0.

 procedure division using lnkString returning lnkResult.
* Code to compare lnkElement to self and return a result
     exit method.
 end method "lessThanOrEqual".

Use the following interface to implement the "lessThanOrEqualbyLengthValue" method:

 method-id. "lessThanOrEqualbyLengthValue".
 linkage section.
 01 lnkElement                  object reference. 
 01 lnkSize                     pic x(4) comp-5.
 01 lnkResult                   pic x comp-5.
  88 isLessThanOrEqual          value 1. 
  88 isNotLessThanorEqual       value 0. 
 procedure division using lnkElement lnkSize 
                returning lnkResult. 
* Code to compare lsElement to self and return 
* isNotLessThanOrEqual if lsElement is greater than
* self. The lsSize parameter is the value returned
* by element in response to the "size" message.
    exit method.
 end method "lessThanOrEqualbyLengthValue". 

Hashing Elements

The dictionary and set classes use hash values to store and retrieve elements. The default "hash" mechanism in Base returns the object reference to the object.

You will often need to override the default with a hash mechanism which creates a hash value using the object's instance data. There are two reasons for this:

Implement the interface to "hash" as follows:

 method-id. "hash".
 linkage section.
 01 lnkHashValue                pic s9(9) comp-5.
 procedure division returning lnkHashValue. 
* Code to return a hashvalue based on object's attributes.
    exit method.
 end method "hash". 

Separate objects that have the same value must always return identical results from "hash". Hash values do not have to be unique for each element, but the more duplicate hash values a dictionary contains, the less efficient its storage and retrieval of elements. Hash values should always be positive numbers.

Display Mechanisms

There are two mechanisms you can use to display the contents of a collection. You can send the message "display" to the collection or you can display the collection on a Listbox.

Using the display Message

If you send the "display" message to a collection, it displays on the console all the elements of the collection between parentheses "()". To do this, it sends the message "display" to every element in the collection. The default "display" method in Base displays a description of the object. For example, if you had a CheckAccount object, the default "display" method would show:

an instance of the class checkaccount

CharacterArray and the COBOL intrinsic classes implement their own "display" methods that display their contents. You can implement "display" in your own objects using the following interface:

 method-id. "display".
 ...
 procedure division.
* Code to display self on the console. 
    exit method.
 end method "display". 

Display on a Listbox

You can display a collection of CharacterArrays or intrinsic data by passing it to a Listbox with the "setContents" message. The Listbox displays each element of the collection as a string on a separate line.

Collection Sort Methods

Instances of SortedCollection sort elements into order as they are added. The default sorting mechanism provided sends the "lessThanOrEqual" message (if a collection "ofReferences") or the "lessThanOrEqualbyLengthValue" message (if a collection "ofValues") to elements to compare their relative values (see the section Relative Value of Objects).

You can set your own sort method for an instance of SortedCollection. The method you provide must compare two elements and return a value indicating which element should appear before the other.

To override the default sort method you must create a Callback for your method and pass the Callback to the SortedCollection.

Implement the interface to your sort method as follows:

 method-id. "sortMethod".
 linkage section.
 01 lnkElement1              object reference. 
 01 lnkElement2              object reference. 
 01 lnkResult                pic x comp-5.
  88 element1first           value 1. 
  88 element2first           value 0. 
 procedure division using lsElement1 lsElement2  
                returning lnkResult. 
* Code to compare lsElement1 to lsElement2 and set 
* lnkResult to determine which element should precede 
* the other. 
    exit method.
 end method "sortMethod". 

Iterator Methods

The collection classes implement four iterator methods which enable you to access all the elements of your collection by sending a single message to the collection. To use any of the iterator methods you need to provide a Callback object. The Callback is passed each element of the collection as a parameter in turn.

This section shows you the interface the method in the Callback must have to work with the iterator methods. The iterator methods provided are:

"do" Passes every element in the collection to a callback.
"select" Passes every element in the collection to a callback. Any elements for which the callback returns "true" (by setting a PIC X COMP-5 to value 1) are stored in a new subcollection.
"reject" Passes every element in the collection to a callback. Any elements for which the callback returns "false" (by setting a PIC X COMP-5 to value 0) are stored in a new subcollection.
"collect" Passes every element in the collection to a callback. The Callback returns an object (which can be the same or different to the original element). All the objects returned are collected into a new collection of the same type as the original collection.

Implement the interface for a callback method for "do" as follows:

 method-id. "doMethod".
 linkage section.
 01  lnkElement               object reference. 
 procedure division using lnkElement. 
* Any code to process the element. 
    exit program.
 end method "doMethod". 

Implement the interface for a callback method for "select" as follows:

 method-id. "selectMethod".
 linkage section.
 01 lnkElement               object reference. 
 01 lnkselectResult          pic x comp-5.
  88  selectThisElement      value 1. 
  88  dontSelectElement      value 0. 
 procedure division using lnkElement 
                    returning lnkSelectResult. 
* Code to determine whether lnkElement should be included in
* the new subcollection. 
    exit method.
 end method "selectMethod". 

Implement the interface for a callback method for "reject" as follows:

 method-id. "rejectMethod".
 linkage section.
 01 lnkElement               object reference. 
 01 lnkRejectResult          pic x comp-5.
  88 rejectThisElement       value 1. 
  88 dontRejectElement       value 0. 
 procedure division using lnkElement 
                    returning lnkRejectResult. 
* Code to determine whether lnkElement should be included in
* the new subcollection. 
    exit method.
 end method "rejectMethod". 

Implement the interface for a callback method for "collect" as follows:

 method-id. "collectMethod".
 linkage section.
 01 lnkElement               object reference. 
 01 lnkReturnElement         object reference. 

 procedure division using lnkElement 
                    returning lnkReturnElement.
* Code to return an element for the new collection.
    exit method.
 end method "collectMethod". 

Copyright © 2004 Micro Focus International Limited. All rights reserved.