IBM
Contents Index Previous Next



The AccessVisitor Class


TTCN Access is a large class library, with over 600 classes, and therefore we also need suitable tools for simplifying the creation of TTCN Access applications. The preferred solution is the usage of the class AccessVisitor, which definition is available in the C++ header file ITEXAccessVisitor.hh.

The AccessVisitor class is a very close relative to the design pattern `Visitor' (described by, for instance, Gamma, Helm, Johnson and Vlissides in `Design Patterns - Elements of reusable software', Addison-Wesley1994). The difference is that the TTCN Access classes contain runtime type information which eliminates the need for them to have dependencies to the Visitor class (and therefore there is no need for `Accept' methods in the visited classes).

An object of a class which is derived from the class AccessVisitor is later on referred to as a `visitor'.

AccessVisitor Class Members

Common Classes

The AccessVisitor class has two methods for visiting objects of common classes AccessSuite and AccessNode. These are declared

public:
void Visit( const AccessNode );
void Visit( const AccessSuite & );

and calling them with, will start a chain of calls in the visitor which effectively is a pre-order traversal of the subtree of the AccessNode, or the complete syntactical tree of the AccessSuite.

TTCN/ASN.1 Derived Classes

The AccessVisitor class has one virtual member function for each TTCN and ASN.1 derived class in TTCN Access. Each of the member functions are declared

public:
virtual void Visit<class>( const <class> & );

Example 182

Visitor member method for an Identifier (excerpt from ITEXAccessVisitor.hh):

class AccessVisitor 
{
public:
    ...
  virtual void VisitIdentifier(const Identifier&);
  virtual void VisitVerdict(const Verdict&);
    ...
};

The base class implementation of this method calls the related Visit<child-class> function for all of the child objects to the current object.

If you need to change the behavior, for instance in order to generate some code or report from the TTCN Access Suite, just derive a new class from the AccessVisitor class and override the relevant method(s). Call the base class implementation of the method to traverse the children if needed.

Data Members

The AccessVisitor class contains no explicitly declared data members and is therefore stateless. It therefore makes program re-entrance possible, and several visitors may be active in the same tree/document at the same time if needed.

Using the AccessVisitor

The intended usage of the AccessVisitor class is by derivation. Derive one or several specialized classes for each of the purposes which you use TTCN Access. Override the relevant methods.

Use Case - Information Collection

It is simple to create a visitor class for collection of some kind of information from the test suite. The basic method for this is to have a handle or actual information in the visitor class.

Example 183

An information collecting visitor class:

#include <time.h>
#include <iostream.h>
#include <ITEXAccessClasses.hh>
#include <ITEXAccessVisitor.hh>

//
// A visitor which counts the number of testcases 
// in a test suite.

class TestCounter : public AccessVisitor
{
public:
  TestCounter() : _count( 0 ) { }
  ~TestCounter() { cout << _count << endl; }
  void VisitTestCase(const TestCase&) { _count++; }
private:
  unsigned int _count;
};

// Small example usage, no real fault control...

int main( int argc, char ** argv )
{
  AccessSuite suite;
  if ( suite.open( argv[1] ) ) {
    TestCounter testCounter;
    testCounter.Visit( suite );  
  }
  return 0;
}

Use Case - Code Generation

It is likewise simple to create a class for simple code generation. The following example is a class for generation of a c-style declaration which contains a list of all SimpleType names in a test suite.

Example 184

An information collecting visitor class:

#include <stdio.h>
#include <time.h>
#include <ITEXAccessClasses.hh>
#include <ITEXAccessVisitor.hh>

// A visitor which generate a c-style declaration 
// of a null-terminated array of all Simple Type
// Declarations (identifiers) in a test suite.

class ListGenerator : public AccessVisitor
{
public:
  ListGenerator(FILE * f) : _file( f ) {
	   printf( "const char *ids[]={\n");
        }
  void VisitSimpleTypeId(const SimpleTypeId& id) {
 	   printf( "  \"%s\",\n", (const char*)
	 	    id.simpleTypeIdentifier()); 
        }
  ~ListGenerator() { 
	   printf( "  NULL\n};\n"); 
        }
};

// Small example usage, no real fault control...

int main( int argc, char ** argv )
{
  AccessSuite suite;
  if ( suite.open( argv[1] ) ) {
    ListGenerator generator;
    generator.Visit( suite );  
  }
  return 0;
}

The last two examples are not the most efficient implementations due to the fact that they traverse the whole tree even though we are only interested in small parts, and therefore we may want to improve the efficiency. This may be done using any of the techniques described in Optimizing Visitors.

Advanced Use Case - Combined Visitors

More advanced designs may be possible by combining several visitors for performing more advanced tasks. You may for instance have visitors which are parameterized with other visitors for specialized tasks, where you still would want to maintain decent performance and yet not have trade-offs in clarity of the design.

Example 185

A TTCN Interpreter would need a mechanism for building an internal representation of values. Values are always built by using the same structure, but you may wish to have several possible representations of atomic values. A visitor could be used to generate the value objects, where one visitor is used for building the overall structure, and another is used for building individual fields. There are at least two choices available in the design:

In this example, the value building class inherits from the AccessVisitor class and the Visit<xxx> functions are used to generate a new value. The class GciValueBuilder in the example, may visit any structured type and will on the Visit<class> function either create itself a data value, or if it is a structured type, create a dynamic array, and then invoke a new visitor for each of the fields, which results will later be assigned to each of the fields in the same array. The implementation is not present in the example. It is just outlined below.

The solution of using visitors for building the values, removes the switch statements, which otherwise would undoubtedly clobber an implementation which uses Direct Access as described in a previous section.

class GciValueBuilder : public AccessVisitor
{
  // basic structure for building/matching values
  virtual void VisitXxxx( const Xxxx & );
  ...
  // built value
  GciValue * _value;
};

class GciValueBinaryBuilder : public GciValueBuilder
{
  // suitable overrides for efficient binary value
  // handling
  ...
};

class GciValueBigNumBuilder : public GciValueBuilder
{
  // suitable ovverides for a `bignum'
  // implementation
  ...
};

Optimizing Visitors

A visitor is a potentially inefficient way to find and process objects, since it in its unmodified version traverses the whole document, without regarding what parts of the document the inherited visitor is interested in. All optimizations are in the domain of limiting the subtree for which we are traversing. The following examples shows methods for avoiding unnecessary traversal and optimally, constant time access.

Example 186

Optimizing a visitor class to avoid traversal of all parts but the declarations part, may improve performance for suite traversal by over 100 times for some fairly representable suites (since most suites contain more and larger syntactical trees in the dynamic part than in all other parts, possibly with the exception of ASN.1 constraint declarations).

class DeclarationChecker : public AccessVisitor 
{
 public:
  void VisitSuiteOverviewPart( 
	 	 const SuiteOverViewPart& ) { }
  void VisitConstraintsPart( 
	 	 const ConstraintsPart & ) { }
  void VisitDeclarationsPart (
	 	 const DeclarationsPart & ) { }

  // Add the functions which actually do processing
  // below ...
};

Optimizing a visitor class to traverse only parts we are interested in, is also possible, by using one or a few levels of Direct Access instead of the default traversal.

Example 187

This example skips all traversal down to the Simple Type Definitions table, and only traverses those.

class SimpleTypeIdPrinter : public AccessVisitor 
{
 public:
  void VisitASuite( 	 	 const ASuite& );
  void VisitSimpleTypeId ( 	 	 const SimpleTypeId & );
};

void 
SimpleTypeIdPrinter::VisitASuite(const ASuite & s)
{
  VisitSimpleTypeDefs( s.declarationsPart().
	 	 	 definitions().
	 	 	 ts_TypeDefs().
	 	 	 simpleTypeDefs());
}

void
SimpleTypeIdPrinter::VisitSimpleTypeId( const 
	 	 	 	 SimpleTypeId & id )
{
  cout 	 << "Id: " 
	 << id.simpleTypeIdentifier() 
	 << endl;
}

Finally, you may combine several visitors into one visitor, thus avoiding multiple passes when processing a suite. This implies that you define several classes which are not truly visitors, and define a inherited class from AccessVisitor which calls methods in these classes.

Example 188

Two objects driven by a visitor, thus performing two passes on one traversal.

class IdOperation
{
 public:
  virtual void AtIdentifier(const Identifier&) = 0;
};

// A IdOperation

class IdCounter : public IdOperation
{
 public:
  IdCounter( ) : _count( 0 ) { } 
  void AtIdentifier( const Identifier & id )
     { _count++; }
  ~IdCounter( ) { cout << _count << endl; }
 private:
  unsigned int _count;
};

// Another IdOperation

class IdPrinter : public IdOperation
{
  public:
   void AtIdentifier( const Identifier & id )
	 { cout << id << endl; }
};

// A class which drives up to IdOpDriverMax 
// IdOperations

const int IdOpDriverMax = 10;

class IdOpDriver : public AccessVisitor
{
 public:
  IdOpDriver() : _ops_used(0) { }
  void VisitIdentifier( const Identifier & id ) {
    for (unsigned int op = 0 ; op < _ops_used; ++op)
      _ops[op].AtIdentifier( id );
  }
  void AddIdOp( IdOperation * op )
	 { _ops[_ops_used++] = op; }
 private:
  IdOperation  _ops[IdOpDriverMax];
  unsigned int _ops_used;
};

// Main routine which opens a suite and applies two
// IdOperations.

int main( int argc, char ** argv )
{
  AccessSuite suite;
  if ( suite.open( argv[1] ) ) {
    IdCounter  counter;
    IdPrinter  printer;
    IdOpDriver driver;
    driver.AddIdOp( &counter );
    driver.AddIdOp( &printer );
    driver.Visit( suite );
    suite.close( );
  }
  return 0;
}
	 


http://www.ibm.com/rational
Contents Index Previous Next