Using Caché Objects
Defining Method and Trigger Generators
[Back] [Next]
   
Server:docs2
Instance:LATEST
User:UnknownUser
 
-
Go to:
Search:    

A method generator is a specific kind of method that generates its own runtime code. Similarly, a trigger generator is a trigger that generates its own runtime code. This chapter discusses them and covers the following topics:

This chapter primarily discusses method generators, but the details are similar for trigger generators.
Also see the chapter Defining and Calling Methods and see Adding Triggers in the chapter “Other Options for Persistent Classes.”
When viewing this book online, use the preface of this book to quickly find other topics.
Introduction
A powerful feature of Caché is the ability to define method generators: small programs that are invoked by the class compiler to generate the runtime code for a method. Similarly a trigger generator is invoked by the class compiler and generates the runtime code for a trigger.
Method generators are used extensively within the Caché class library. For example, most of the methods of the %Persistent class are implemented as method generators. This makes it possible to give each persistent class customized storage code, instead of less efficient, generic code. Most of the Caché data type class methods are also implemented as method generators. Again, this gives these classes the ability to provide custom implementations that depend on the context in which they are used.
You can use method and trigger generators within your own applications. For method generators, a common usage is to define one or more utility superclasses that provide specialized methods for the subclasses that use them. The method generators within these utility classes create special code based on the definition (properties, methods, parameter values, etc.) of the class that uses them. Good examples of this technique are the %Populate and %XML.Adaptor classes provided within the Caché library.
Basics
A method generator is simply a method of a Caché class that has its CodeMode keyword set to “objectgenerator”:
Class MyApp.MyClass Extends %RegisteredObject
{
Method MyMethod() [ CodeMode = objectgenerator ]
    {
        Do %code.WriteLine(" Write """ _ %class.Name _ """")
        Do %code.WriteLine(" Quit")
        Quit $$$OK
    }
}
When the class MyApp.MyClass is compiled, it ends up with a MyMethod method with the following implementation:
 Write "MyApp.MyClass"
 Quit
Note:
The value of CodeMode in the previous example is “objectgenerator”, since this method generator uses the preferred, object-based, method generator mechanism. Prior to version 5 of Caché, there was a different preferred mechanism, in which the value of CodeMode was “generator”. While the older mechanism is preserved for compatibility, new applications should use “objectgenerator”.
You can also define trigger generators. To do so, use CodeMode = “objectgenerator” in the definition of a trigger. The values available within your trigger are slightly different than those in a method generator.
How Generators Work
A method generator takes effect when you compile a class. The operation of a method generator is straightforward. When you compile a class definition, the class compiler does the following:
  1. It resolves inheritance for the class (builds a list of all inherited members).
  2. It makes a list of all methods specified as method generators (by looking at the CodeMode keyword of each method).
  3. It gathers the code from all method generators, copies it into one or more temporary routines, and compiles them (this makes it possible to execute the method generator code).
  4. It creates a set of transient objects that represent the definition of the class being compiled. These objects are made available to the method generator code.
  5. It executes the code for every method generator.
    If present, the compiler will arrange the order in which it invokes the method generators by looking at the value of the GenerateAfter keyword for each of the methods. This keyword gives you some control in cases where there may be compiler timing dependencies among methods.
  6. It copies the results of each method generator (lines of code plus any changes to other method keywords) into the compiled class structure (used to generate the actual code for the class).
    Note that the original method signature (arguments and return type), as well as any method keyword values, are used for the generated method. If you specify a method generator as having a return type of %Integer, then the actual method will have a return type of %Integer.
  7. It generates the executable code for the class by combining the code generated by the method generators along with the code from all the non-method generator methods.
The details are similar for trigger generators.
Values Available to Method Generators
The key to implementing method generators is understanding the context in which method generator code is executed. As described in the previous section, the class compiler invokes the method generator code at the point after it has resolved class inheritance but before it has generated code for the class. When it invokes method generator code, the class compiler makes the following variables available to the method generator code:
Variables Available to Method Generators
Variable Description
%code An instance of the %Stream.MethodGenerator class. This is a stream into which you write the code for the method.
%class An instance of the %Dictionary.ClassDefinition class. It contains the original definition of the class.
%method An instance of the %Dictionary.MethodDefinition class. It contains the original definition of the method.
%compiledclass An instance of the %Dictionary.CompiledClass class. It contains the compiled definition of the class being compiled. Thus, it contains information about the class after inheritance has been resolved (such as the list of all properties and methods, including those inherited from superclasses).
%compiledmethod An instance of the %Dictionary.CompiledMethod class. It contains the compiled definition of the method being generated.
%parameter An array that contains the values of any class parameters indexed by parameter name. For example, %parameter("MYPARAM"), contains the value of the MYPARAM class parameter for the current class. This variable is provided as an easier alternative to using the list of parameter definitions available via the %class object.
Values Available to Trigger Generators
Like methods, triggers can be defined as generators. That is, you can use CodeMode = “objectgenerator” in the definition of a trigger. The following variables are available within the trigger generator:
Added Variables Available to Trigger Generators
Variable Description
%code, %class, %compiledclass, and %parameter See the preceding section.
%trigger An instance of the %Dictionary.TriggerDefinition class. It contains the original definition of the trigger.
%compiled%trigger An instance of the %Dictionary.CompiledTrigger class. It contains the compiled definition of the trigger being generated.
Defining Method Generators
To define a method generator, do the following:
  1. Define a method and set its CodeMode keyword to “objectgenerator”.
  2. In the body of the method, write code that generates the actual method code when the class is compiled. This code uses the %code object to write out the code. It will most likely use the other available objects as inputs to decide what code to generate.
The following is an example of a method generator that creates a method that lists the names of all the properties of the class it belongs to:
ClassMethod ListProperties() [ CodeMode = objectgenerator ]
{
    For i = 1:1:%compiledclass.Properties.Count() {
        Set prop = %compiledclass.Properties.GetAt(i).Name
        Do %code.WriteLine(" Write """ _ prop _ """,!")
    }
    Do %code.WriteLine(" Quit")
    Quit $$$OK
}
This generator will create a method with an implementation similar to:
 Write "Name",!
 Write "SSN",!
 Quit
Note the following about the method generator code:
  1. It uses the WriteLine method of the %code object to write lines of code to a stream containing the actual implementation for the method. (You can also use the Write method to write text without an end-of-line character).
  2. Each line of generated code has a leading space character. This is required because Caché ObjectScript does not allow commands within the first space of a line. This would not be the case if our method generator is creating Basic or Java code.
  3. As the lines of generated code appear within strings, you have to be very careful about escaping quotation mark characters by doubling them up ("").
  4. To find the list of properties for the class, it uses the %compiledclass object. It could use the %class object, but then it would only list properties defined within the class being compiled; it would not list inherited properties.
  5. It returns a status code of $$$OK, indicating that the method generator ran successfully. This return value has nothing to do with the actual implementation of the method.
Method Generators for Other Languages
You can generate code for different languages. To do so, set the Language property of the %code object to specify the target language.
By default, the language for the generated code is the same as the language used to write the code generator method (specified by the Language keyword).
Specifying CodeMode within a Method Generator
By default, a method generator will create a “code” method (that is, the CodeMode keyword for the generated method is set to “code”). You can change this using the CodeMode property of the %code object.
For example, the following method generator will generate an ObjectScript expression method:
Method Double(%val As %Integer) As %Integer [ CodeMode = objectgenerator ]
{
    Set %code.CodeMode = "expression"
    Do %code.WriteLine("%val * 2")
}
Generators and INT Code
For method and trigger generators, it can be very useful to display the corresponding INT code after compiling the class. See Displaying INT Code in the chapter Useful Skills to Learn in Caché Programming Orientation Guide.
Note that if the generator is simple enough to be implemented in the kernel, there is no generated .INT code for it.
Generator Methods and Subclasses
This section discusses topics specific to generator methods in subclasses of the class in which they were defined.
It is necessary, of course, to compile any subclasses after compiling the superclass.
Method Regeneration in Subclasses
When you subclass a class that defines generator methods, Caché uses the same compilation rules that are described earlier in this chapter. Caché does not, however, recompile a method in a subclass if the generated code looks the same as the superclass generated code. This logic does not consider whether the include files are the same for both classes. If the method uses a macro that is defined in an include file and if the subclass uses a different include file, Caché would not recompile the method in the subclass. You can, however, force the generator method to be recompiled in every class. To do so, specify the method keyword ForceGenerate for that method. There may be additional scenarios where this keyword is needed.
Invoking the Method in the Superclass
If you need a subclass to use the method generated for the superclass, rather than a locally generated method, do the following in the subclass: define the generator method so that it just returns $$$OK, as in the following example:
ClassMethod Demo1() [ CodeMode = objectgenerator ]
{
    quit $$$OK
}
Removing a Generated Method
You can remove a generated method from a subclass, so that it cannot be invoked in that class. To do so, when you define the generator method in the superclass, include logic that examines the name of the current class and generates code only in the desired scenarios. For example:
ClassMethod Demo3() [ CodeMode = objectgenerator ]
{
    if %class.Name="RemovingMethod.ClassA" {
        Do %code.WriteLine(" Write !,""Hello from class: " _ %class.Name _ """")
    }
    quit $$$OK
}
If you try to invoke this method in any subclass, you receive the error <METHOD DOES NOT EXIST>.
Note that this logic is subtly different from that described in the previous section. If a generator method in a given class exists but has a null implementation, the method of the superclass, if any, is used instead. But if a generator method in a given class does not generate code for a given subclass, the method does not exist in that subclass and cannot be invoked.