XData Studio Assist

Some days ago Alberto has written a very interesting article about the usefulness of ObjectGenerators available in Caché.

In this article, Alberto shows an example RuleEngine based on XData, and how it is possible to improve the process of filling such rules. Caché Studio has a Studio Asist for XData; you may have already seen examples of it if you use ZEN or %Installer.Manifest.

We can use the %XGEN.AbstractDocument class for our task. Unfortunately, InterSystems has marked these classes (all of classes in %XGEN, in fact) as for internal use only. With %XGEN, we should not think about parsing XML in XData, and we have some callbacks, which calls in a parse process.

Consider the code below as an example:

Class Dev.Definition Extends %XGEN.AbstractDocument [ System = 3 ]
{

Parameter NAMESPACE = "RuleEngine";

Parameter XMLNAMESPACE = "RuleEngine";

Parameter ROOTCLASSES As STRING = "Dev.Definition:Definition";

Property Identifier As %String(MAXLEN = 200, XMLPROJECTION = "ATTRIBUTE");

Property Rules As list Of Rule(XMLPROJECTION = "ELEMENT");

/// This method is called when a class containing an XGEN
/// document is compiled. It is called <em>before</em> the <method>%GenerateCode</method> method
/// processes its children.<br>
/// <var>pTargetClass</var> is the class that contains the XGEN document.<br/>
/// <var>pCode</var> is a stream containing the generated code.<br/>
/// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/>
/// A subclass can provide an implementation of this method that will
/// generate specific lines of code.<br/>
Method %OnBeforeGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status
{
    do pCode.WriteLine("#define AddLog(%line) set log($i(log))=""[""_$zdatetime($ztimestamp,3)_""] ""_%line")
    do pCode.WriteLine(..%Indent(1)_"Set tSC = $$$OK ")
    do pCode.WriteLine(..%Indent(1)_"try { ")
    quit $$$OK
}

/// This method is called when a class containing an XGEN
/// document is compiled. It is called <em>after</em> the <method>%GenerateCode</method> method
/// processes its children.<br>
/// <var>pTargetClass</var> is the class that contains the XGEN document.<br/>
/// <var>pCode</var> is a stream containing the generated code.<br/>
/// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/>
/// A subclass can provide an implementation of this method that will
/// generate specific lines of code.<br/>
Method %OnAfterGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status
{
    do pCode.WriteLine(..%Indent(1)_"} catch ex { set tSC = ex.AsStatus() }")
    do pCode.WriteLine(..%Indent(1)_"quit tSC")
    quit $$$OK
}

}
Class Dev.Rule Extends Dev.Sequence [ System = 3 ]
{

Property Title As %String(XMLPROJECTION = "ATTRIBUTE");

Property Condition As %String(XMLPROJECTION = "ATTRIBUTE");

Property Actions As list Of Action(XMLPROJECTION = "ELEMENT");

/// This method is called when a class containing an XGEN
/// document is compiled. It is called <em>before</em> the <method>%GenerateCode</method> method
/// processes its children.<br>
/// <var>pTargetClass</var> is the class that contains the XGEN document.<br/>
/// <var>pCode</var> is a stream containing the generated code.<br/>
/// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/>
/// A subclass can provide an implementation of this method that will
/// generate specific lines of code.<br/>
Method %OnBeforeGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status
{
    do pCode.WriteLine(..%Indent()_"If ("_..Condition_") { set actionCounter=0 ")
    do pCode.WriteLine(..%Indent(1)_"$$$AddLog(""Rule: "_..Title_" "")")
    quit $$$OK
}

/// This method is called when a class containing an XGEN
/// document is compiled. It is called <em>after</em> the <method>%GenerateCode</method> method
/// processes its children.<br>
/// <var>pTargetClass</var> is the class that contains the XGEN document.<br/>
/// <var>pCode</var> is a stream containing the generated code.<br/>
/// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/>
/// A subclass can provide an implementation of this method that will
/// generate specific lines of code.<br/>
Method %OnAfterGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status
{
    do pCode.WriteLine(..%Indent()_"}")
    quit $$$OK
}

}
Class Dev.Action Extends Dev.RuleEngineNode [ System = 3 ]
{

Parameter NAMESPACE = "RuleEngine";

Property Type As %String(VALUELIST = ",call,return", XMLPROJECTION = "ATTRIBUTE");

Property Class As %String(XMLPROJECTION = "ATTRIBUTE");

Property Method As %String(XMLPROJECTION = "ATTRIBUTE");

Property Args As %String(XMLPROJECTION = "ATTRIBUTE");

/// Generate code for this node.<br/>
/// This method is called when a class containing an XGEN
/// document is compiled.<br/>
/// <var>pTargetClass</var> is the class that contains the XGEN document.<br/>
/// <var>pCode</var> is a stream containing the generated code.<br/>
/// <var>pDocument</var> is the top-level XGEN document object that contains this node.<br/>
/// A subclass will provide an implementation of this method that will
/// generate specific lines of code.<br/>
/// For example:
/// <example>
/// Do pCode.WriteLine(..%Indent()_"Set " _ ..target _ "=" _ $$$quote(..value))
/// </example>
Method %OnGenerateCode(pTargetClass As %Dictionary.CompiledClass, pCode As %Stream.TmpCharacter, pDocument As %XGEN.AbstractDocument) As %Status
{
    do pCode.WriteLine(..%Indent()_"$$$AddLog(""Action: ""_$i(actionCounter))")
    if ..Type="call" {
        do pCode.WriteLine(..%Indent() _ "do $classmethod("_$$$quote(..Class)_", "_$$$quote(..Method)_", "_..Args_")")
    }
    elseif ..Type="return" {
        do pCode.WriteLine(..%Indent() _ "quit ")
    }   
    Quit $$$OK
}

}

And a final class, which used to generate Evaluate method

Class Dev.RuleEngine Extends %RegisteredObject [ System = 3 ]
{

XData XMLData [ XMLNamespace = RuleEngine ]
{
<Definition>
</Definition>
}

ClassMethod Evaluate(context, log) [ CodeMode = objectgenerator ]
{
    Quit ##class(Dev.Definition).%Generate(%compiledclass, %code, "XMLData")
}

}

Yes, our object generator is now split into multiple classes, but in this case our code is placed close to the logic, and much less code was written. And when we decide to add some new type in Action, we just can do it in the same class.

Our XML looks a bit different, but now with intellisense, and result method Evaluate exactly the same.

I have attached dev.xml.zip with my code, which depends Alberto's code..

Comments