JeeWiz Home  
The Model-Driven System Builder

JeeWiz Architect's Guide
 
Contents  >   8.  Templates and Velocity Features
 


8.3 JeeWiz Methods

JeeWiz provides an extension to standard Velocity to support object-oriented methods on model objects and JeeWiz controls. This section describes this facility and gives guidance on how to use methods.

 8.3.1  Overview
 8.3.2  Constructing Objects To Use Methods On
 8.3.3  Syntax
 8.3.4  Invoking Methods - Explicit
 8.3.5  Invoking Methods - Implicit
 8.3.6  Evaluation Context
 8.3.7  $super
 8.3.8  dump
 8.3.9  Local Methods Compared to Local Macros
 8.3.10  Details of Operation

8.3.1  Overview
Methods can be executed on generate-time 'objects', which in JeeWiz templates and patterns means model objects and JeeWiz controls. This feature is additional to calls into Java-language methods, which are still available on model objects (and of course other, non-JeeWiz objects).

The only other Velocity feature for defining named sequences of Velocity script is the '#macro' feature. JeeWiz methods add the extra feature of being able to attach named scripts to a particular object type - in other words, the JeeWiz method feature is object-oriented. There are other differences between macros and methods, as described below.

The brief overview of this facility is as follows:
  1. Templates and patterns invoke Velocity methods using the normal method invocation syntax, as along as an appropriate definition can be found by JeeWiz.

    For example, in $myObject.meth(), $myObject is the invoking object. If this is a model object or a control, JeeWiz starts looking for an appropriate Velocity-language method.
  2. Methods can be accessed in the following ways:
    • By a lookup on the type of the invoking object.

      For model objects, methods are located in an appropriate file called 'component.methods'. 'component.methods' is a fixed name. For JeeWiz controls, the methods are in a ".control" file that defines the features of the control.

      We talk of object-based access for this type of method access.
    • If the object-based lookup does not find an appropriate method, a method declared in the same file as the call can be used. Methods located in this way are called 'locally-accessed methods'.
    • If the method is still not found, the aggregated system methods from 'system.methods' files will be used. By 'aggregated', we mean that the system.methods files are read in order, lowest-precedence first. As each system.methods file is read, new methods are added to the aggregate. Methods that already exist (same name, case-sensitive, and same number of parameters) are overridden by the higher-precedence definition.
    The above are in precedence order. In other words, if we are looking for method M(), an M() method defined in component.methods will override an M() defined in the local file, which will override an M() global method.
  3. Multiple methods can be defined in a file. The syntax is very similar to macros: '#method' as the Velocity directive instead of '#macro', followed by a list of parameters, then the body of the method, terminated by #end:
    #method( methodName [$param]... )
    ... the body of the method ...
    #end
  4. Methods are invoked by the normal method invocation syntax in Velocity. So
    $object.methodName( "arg1" $arg2 )
    will invoke the method named 'methodName' with one parameter defined on $object.
  5. Methods are identified by
    • their location - either locally accessed or, for object-based methods, the object type for the object (e.g. for a model object, the effective tag of XML definition for the element)
    • the method name
    • the number of parameters
    Note that the number of parameters is important in picking which method to use, so the following method declarations create distinct methods:
    #method( methodName $p1 )
    #method( methodName $p1 $p2 )
    
  6. Unlike methods defined in Java, which are distinguished based on the type of the parameters, JeeWiz methods ignore the type of parameters (because Velocity is an untyped language). The only considerations in selecting a method to execute are as described in the previous section. This means that if
    $object.methodName( "arg1" 0 )
    calls a certain method, then
    $object.methodName( 2 "arg2" )
    will call the same method.
  7. If there is a suitable method that can be access locally (i.e. defined in the same file as the invocation, and for the correct name and number of parameters) this method is used in preference to searching for an object-based method. In other words, regardless of the type of the object used to invoke the method - as long as it is a model object or JeeWiz control - an available locally-accessed method takes priority over an object-based access. The usual rules for $this and $self apply, so, for example, the object the method is invoked on is passed into the method as '$self'.
  8. For object-based methods multiple component.methods files can apply to one object. The complete set of available methods is constructed by aggregating the methods across the component.methods files. The method files are located depending on the type of object:
    1. For model objects, methods are defined in component.methods files in a subdirectory of the template directory. The subdirectory name is defined by the object's type. (And two attempts are made to find the subdirectory, using the xmlName first and then the javaVarName. So in the case of a business method object, "business-method" is tried first, the "businessMethod".)
    2. For controls, methods are define in x.control, where 'x' is replaced by the control's name.
    They are defined in different template directories and found using the template.properties file as described in the section on Finding Files. To recap:
    1. First, search down the template directories.
    2. Second, honour diversions specified in the template.properties found in a template directory. In other words, insert an additional search if an includes line is found, and restart the search with the new object type name if a goto line is found.
    For example, for a (mythical) template directory stack of
    jeewiz/resources/j2ee/control
    jeewiz/resources/bizobject/control
    jeewiz/resources/base/control
    
    and a template type of 'businessMethod' the component.methods files that will be read, if they are present, are as follows (we gloss over the javaVarName lookup if the xmlName subdirectory is missing):
    jeewiz/resources/j2ee/control/business-method/component.methods
    jeewiz/resources/bizobject/control/business-method/component.methods
    jeewiz/resources/base/control/business-method/component.methods

    Any component.methods files found in this search are aggregated to create the complete set of Velocity methods for this object, with files found earlier in the search being more precedent. In other words, if a method with the same name and number of parameters is found, the one found first in the search is used; the second one is overridden and only available for this object type using $super in object methods.

  9. Although JeeWiz provides a 'local' method feature, it does not support 'global' method declaration. In contrast, Velocity has the ability to define global macros in VM_global_library.vm. If a 'global' facility is required for a particular meta-class, it can be simulated using the template.properties feature, by going to, or including, a base element type.
  10. For meta-classes and JeeWiz controls defined in Java, methods defined via '#method' take precedence over available methods defined in Java.

8.3.2  Constructing Objects To Use Methods On
Methods can be used on model objects and JeeWiz controls. Both types of objects can be defined in Java, or defined on an ad-hoc basis.

Model objects explicitly defined in Java are created by reading in a model, or by creation in a pattern, when the XML element maps to a meta-modelled class. Ad-hoc model objects are created when XML is read in but there is no meta-class to match the XML element. In this case, the object created is of the type ExtraBuildComponent - which is to all intents and purposes just a basic model object, which supports all the model object helper methods.

JeeWiz controls can be created by calling the getNewControl( String controlName ) method on a model object:
$this.getNewControl( "myControl" )
This creates a new control. See the chapter on JeeWiz controls for details.
8.3.3  Syntax
The declaration of a method must only be done as a top-level directive in a component.methods file; in other words, #method directives cannot be
  • embedded in any other Velocity directive (e.g. #method within a #method or #macro block)
  • defined in any file other than component.methods.
Although the method name is normally defined with a name (rather than variable reference - i.e. with a preceding '$') and its parameters are normally defined with a variable reference (rather than being preceded by a '$'), either names and variable references can be used to define the method name and the parameters. In other words, the complete syntax for the method declaration is:
#method( [$]methodName[-] [[$]param]... )
... the body of the method ...
#end
The '$' is removed from both names - of the method and of the parameters.

The '[-]' in the above syntax indicates a special feature to trim the output of a method. Velocity allows the '-' character in variable references, and JeeWiz takes advantage of this to allow the method declaration to indicate the method is intended to be used in-line. In this case, any whitespace is trimmed from the end of the method's output; note - the start of the method's output is not affected. For example, a method declared as follows
#method( inlineMethod- )
a
#end
when evaluated in the following line
Give me $c.inlineMethod() 'B'
produces
Give me a 'B'
If the '-' were omitted from the method name, the output from 'inlineMethod' would include the trailing newline so the calling line would produce
Give me a
 'B'
The 'trim' feature is asymmetrical - only removing trailing whitespace - because it is harder to control the output at the end of the line, because we tend to write output on one line and then put a method invocation on its own source line too. Without this feature, this would naturally result in two line-breaks, almost certainly one more than was intended.

As this example shows, any number of parameters, including 0, is allowed.

Note that this feature is triggered by appending the '-' to the method name only - and to the final character of the name being '-'. If a method has a '-' inside the name (which is not recommended but supported) then it becomes part of the name, and must be used in the invocation and does not trigger the inline feature. If a parameter is declared with a trailing '-' (e.g. '$a-'), then the '-' is part of the parameter name and must be used in references ( e.g. '$a-' ).

The method body can be any legal Velocity script. Methods can take advantage of Indentation in Scripts to lay out the script with leading tabs discarded.
8.3.4  Invoking Methods - Explicit
Methods are invoked in the same way as other Velocity methods:
$object.methodName( [args...] )
We say the the method is being executed, or evaluated, on the object.

If no parameters are defined, the parentheses '()' are still required to trigger the method invocation.

As with other methods accessible by Velocity, methods declared via the JeeWiz extension are identified by the (case-sensitive) name and number of parameters. Of course, there is no type matching with JeeWiz methods, because there is no type information in the method declaration.


8.3.5  Invoking Methods - Implicit
Velocity provides the feature to map a property access on an object (e.g. $obj.prop) into a call to a getter on that object (obj.getProp()). JeeWiz enhances this to allow the getter to be in Velocity as well as in Java.

The rules are different for model objects and controls, for which the rules are described here. For model objects, the rules are as follows:

  1. First, if a Java getter is available for the property, it is invoked and whatever it returns (even if that is null) is the value.
  2. Next, if a property is in the HashMap, that is returned. These can be any object, not just strings. It is possible to put a null value into a HashMap under a given key; if such an entry is retrieved in this case, the value is ignored. (The value in the HashMap is available via get(propertyName).)

    Values returned in this step can be any object, not just strings.

  3. Now the engine looks for a suitable Velocity getter exists on this control (or on its extensions), following the JavaBeans naming convention. If a method exists, it is called. The expectation is that the Velocity method will execute #return( $expr ); if so, the expression in the #return is returned as the value - even if it is null. If not, then any output of the method is returned instead. For example, if getProp() is defined as follows:
    #method( getProp )
        RETURNED_VALUE
    #end
    then $myControl.prop will return "RETURNED_VALUE".
  4. Finally, if the property is the name of a 'list' and there is precisely one entry in the list, that entry is returned. For example, say we have the native XML for a model as follows
    <myObject>
        <myNested prop="1">
    </myObject>
    This contains a 'myNested' list with precisely one item. This step will recognise this pattern and for $myObject.myNested will return the 'myNested' model object.

    This feature is enabled by default. To disable it, set the build property singletonListsToProperties to false.


8.3.6  Evaluation Context
Arguments to a method are passed by value. In other words, the Velocity engine evaluates each argument and assigns the value to the corresponding parameter before executing the method body. This is done "quietly" so that, if a null argument is passed there is no error and the method is still evaluated, but the corresponding parameter is null.

[ If you use Velocity macros, you should be aware that methods operate differently to macros in this regard. With macros, you can pass references to objects that are currently undefined without causing an error. A change to the parameter in the macro is reflected in the calling context. In other words, macros use "call by reference". ]

There is a local context for the evaluation of a method. The details of this context depend on whether the method is being executed on a model object or a JeeWiz control. For a model object, the context contains:
  • The $this variable, which is the same as the object the method is being executed on.
  • The $self variable, which is the same as the object the method is being executed on. In other words, this is a duplicate of $this.
  • The $super variable, which is described below.
  • Any parameters.
  • Local variable created via #set in the method body.
  • A link (i.e. context search delegation to look up any unsatisfied variable references) to the context for the pattern or template being executed. For any variable references not satisfied by the current context, the evaluation delegates to the linked context. This will then pick up values from the model object and its parent contexts (i.e. properties on parent objects or defined in system.properties) as described in Finding a Value
For a JeeWiz control, the context contains:
  • The $this variable, which is a copy of the $this of the enclosing context, which will be the model object the template or pattern is being run on. This approach allows methods on controls to call methods on other controls and have $this be propagated properly.
  • The $self variable, which is the same as the control the method is being executed on.
  • The $super variable, which is described below.
  • Any parameters.
  • Local variable created via unqualified #set directivies (e.g. #set( $localVar = ...) ) in the method body.
  • A link (i.e. context search delegation) to a context containing the properties defined on the control. These properties are (a) those defined in component.properties for the control and (b) any set by '#set' on $self. Note that these properties are preserved across method calls whereas the local variables are not.
  • A link to a context containing the values set at generate time from build properties .jwp files or system.properties. This means that build and system properties are the last port of call for unqualified variable references not resolved previously.
The reason for having '$self' as well as '$this' is to support the following usage:
  • Set values on the object the method is being executed on via '$self'.
  • Use $this to access helper methods.
This makes it possible to have common 'subroutines' which do not need to know whether they are being executed by a control or a model object.

Here is an example of a method that takes a parameter:
#method( generateCallWithHisNameAndMyName $methodName $object )
	${methodName}( "$object.name", "$name" );
#end
This is a two-parameter method that takes the method to call and an object. It generates a call to a method using the $methodName parameter as the name of the method to call. The parameters to the called method in the generated call are the name of the passed object and the name of the current object (either a model object or a control).
8.3.7  $super
The $super variable is put into the evaluation context of a method. This variable can be used to call "overridden" methods.

The $super reference is actually a proxy object that has no other use than to call methods. When this is used - in an invocation like $super.methodName(...) - the action is as follows:
  • The $self variable is substituted for $super. In other words, the actual object used for calculating references in the called method is the $self - the object that the method is executing on.
  • Now the engine looks for a suitable method, which depends on the type of object the current method is running on.
    1. If the current method is executing on a control, the 'super' control is the one before the one that defined the currently executing method. For example:
      Control "myControl" : #method( myMethod )
                                 ... 
                             #end
      Control "myControl2" : #extends( myControl )
                                 ## has no #method( myMethod ) definition
      Control "myControl3" : #extends( myControl2 )
                             #method( myMethod ) 
                                 ... 
                             #end
                             #method( anotherMethod ) $super.myMethod() ... #end
      Control "myControl4" : #extends( myControl3 )
                             #method( myMethod ) 
                                 ... 
                             #end
      Other code:
      	#set( $myC4 = $this.getNewControl( "myControl4" ) )
      	$myC4.anotherMethod()
      
      When $myC4.anotherMethod() executes, the current control is myControl4, but anotherMethod() method is defined on myControl3. Therefore, "the one before that" - the one it extends - is myControl2. So the call to $super.myMethod() starts its search at myControl2. This does not have a myMethod(), so the implementation used is from myControl.

      Notice that this is slightly different from Java, where the method is called 'super'. In JeeWiz, it is the object that is called 'super', so that we can construct the proxy object for the call.

      For example, a valid use of $super is as follows:

      • A variable $c is created using $this.getNewControl( "specific" ).
      • This control extends the "basic" control.
      • Both these controls define myMethod().
      • The reference $super.myMethod() is valid in the myMethod() on the "specific" control; it will invoke myMethod() on the "basic" control.
      This mechanism can be used to 'top and tail' a value: put information before and after the value returned from the super method:
      #return( "Top...${super.methodName()}...AndTail" )
      
    2. The other case is when the current method is executing on a model object. The template directory in which the current method is defined is examined for a 'goto=' in the template.properties file. If there is no such goto, then an exception is thrown - this is an invalid use of $super.
    3. The method, with the correct number of parameters, is looked up starting with the component.methods in the template directory named in the goto. If there is no such method in the indicated directory, or any of the template directories indicated by the 'include's or 'goto' chain, an exception is thrown.
    4. If there is such a method, then that method is executed.

8.3.8  dump
The dump of the aggregate XML includes a section on the methods encountered during the build. The methods listed in the dump are for both controls and model objects.
8.3.9  Local Methods Compared to Local Macros
Locally-defined methods and macros are similar in a number of ways. They are declared in the same sort of syntax (just changing between '#method' and '#macro'). They can both create output using the full Velocity language. They are used in a similar way - to do jobs specific to the template or pattern they are contained in. However, in general, methods are commonly preferred to macros for the following reasons:
  1. For developers used to Java, the object-oriented approach of object-based methods and passing by value are more natural. In macro calls, parameters are passed by reference.
  2. Methods can be distinguished by the number of parameters: methods of the same name and different numbers of parameters are allowed. Macros are not distinguished by their number of parameters: you cannot define macros with different numbers of parameters.
  3. Macros allow access to variables in the current template's context. Methods provide a protected context for each invocation; variables in the calling context are not visible. (But, both macros and methods can still access globally-defined properties like $topComponent or values from system.properties files.
  4. The call mechanism for methods passes the invocation object as the $self variable by default. In other words, during the execution of $myObject.call(), the $self variable will be defined and set to $myObject. Macro calls do not create a $self variable in the context.
  5. Methods can return an expression, using #return(), which can be used in #if or another call without conversion to a string. Macros return a string if used inline.
  6. Under certain conditions, there is a bug in the use of the #for loop in macros.

8.3.10  Details of Operation
This section describes the details of how the method feature is implemented for a model object, with reference to the following diagram:
[1] The Velocity engine - itself Java code - is the running application and operates in two stages.

First, it parses the textual form of the Velocity scripts into an intermediate form using Java objects. (To implement additional directives like #method(), the JeeWiz engine provides additional facilities in the Velocity intermediate form.)  This stage is done once, however many times the script is executed.

The Velocity parser recognises a method call by the pattern
$object.methodName( ... )
[2] The second stage is the execution of the Velocity script. The Velocity processor locates the initial referenced object - $object - as a starting point for looking up methods. In general, there is a difference value for $object for each execution of the Velocity script.

Although this description only uses examples from the first reference (i.e. "$object."), the syntax of Velocity is more general than the example shown: it allows a chain of method calls attached to an initial reference. For example, a chain of methods could be
$object.getMyRelation("mother").methodName(...)
The process used to interpret these chains is exactly the same for subsequent method invocations (e.g. '.methodName(...)' in this example) as for the first method invocation (e.g. '.getMyRelation("mother")').
[3] The JeeWiz enhancement to Velocity first examines $object. It must be a model object or a JeeWiz control to continue this process. Similarly, for chained object invocations, the value of the object preceding the method invocation must be a model object or a JeeWiz control.
[3a] If not - or if the process described below stops at any subsequent point - then the normal Velocity Java method lookup is done.

The implication of this evaluation order is that methods defined in Velocity with #method() take precedence over any applicable method defined in Java.
[4] For model objects and JeeWiz controls, the templateName for the object is determined by calling '$object.getXmlTemplateName()'.

The cases for the templateName, and the resulting templateName, are as follows:

  • Model object whose class is defined in a meta-model: the name of the meta-class
  • Other model objects: the name of the XML element tag used to defined the object
  • Dynamically-created JeeWiz controls: the template name provided in the constructor.
  • Pre-loaded JeeWiz controls: --control, such as 'int-datatype-control'.
[5] A list of template directories is constructed using the techniques described in the Overview above. This list is used to create a second list, of all files named 'component.methods' in the template directories.
[6] Each of the available 'component.methods' files is read. Each 'component.methods' contains any number of method definitions. The methods are used to build a list of Velocity-based methods for the current templateName. Methods are identified both by name and number of parameters. Where there are multiple instances of the methods with the same name and number of parameters, then
  • Methods from component.methods files in "higher" directories (i.e. earlier in the search order) are used in preference to methods in lower directories. Within component.methods files, the precedence is undefined and so duplicate methods should be avoided.
This search order implements overriding between methods defined in different template directories.
[7] Examples of the overriding feature are shown in the diagram:
7a    $object.m( $a1 ) The component.methods file from the j2ee template directory has this method. The similar methods from bizobject and base template directories are overridden.
7b    $object.m( $a1 $a2) Shows that the method 'm' with two parameters is different from the method with one parameter.
7c    $object.m() This examples shows that a method from a lower template directory - in this case the bizobject level - can be referenced if there are no overriding definitions
7d    $object.m3( $a1 $a2 $a3 ) This method goes to yet another level of the template directory stack and also has three parameters.
[8] When $super is used, the context object does not change. In this example, if from a method called via $object as the reference, when "$super.m( $a1 )" is executed, the context object is the same - the value of $object from the original reference. What does change is that the template name is cast down to the next lower template directory in the stack from where the current method was defined. This mechanism is very similar to the semantics of 'super' in Java: the effect of 'super' depends on the class in which the current method is running.

Accordingly,
  • [8a]   the execution of '$super.m($a1)' in the j2ee's method links down to the next available method m($p1), which in this case is in the bizobject directory
  • [8b]   and from there, further execution of '$super.m($a1)' links down to the method in the base directory.
 


Copyright (c) 2001-2008 New Technology/enterprise Ltd.