Notes on the FreeMarker Source Code


The Compiled Form of Templates

Templates are compiled into a tree structure in which child nodes are stored in linked lists (TemplateLinkedList objects, which contain TemplateLink objects). The Template object is the root node.

Each TemplateLink contains a "statement" object to which it delegates its processing tasks. A TemplateLink object's child lists are encapsulated within its statement object. For example, a ListInstruction has one child list representing its loop body; an IfInstruction has two, one for the clause following the "if", and one for the "else" clause.

All the abovementioned classes implement the interface TemplateProcessor, meaning that they have a process() method that sends output to a PrintWriter. When the Template's process() method is called, it calls process() on its TemplateLinkedList, which calls process() on each of its links. Each link calls process() on its statement object, which in turn calls process() on any child lists it owns.

Compilation

The only classes that know about the linked-list structure are TemplateLinkedList, TemplateLink, and LinkedListTemplateBuilder. All the other classes that compose the compiled structure know only that they have been given TemplateProcessors to use at run time. The only class that knows about LinkedListTemplateBuilder is Template; if you wanted to substitute a different kind of compiled structure, you could subclass TemplateBuilder, then subclass Template, overriding only its compileText() method where it instantiates the builder. You'll probably also want to modify whatever TemplateCache you're using so that it instantiates your subclass of Template.

The builder's constructor takes a TemplateParser as an argument. This is an abstract class that can be subclassed to handle different sorts of template syntax. Currently, Template's compileText method instantiates a StandardTemplateParser and passes it to the builder. To change the template syntax, you could subclass TemplateParser, then subclass Template, overriding its compileText() method to instantiate your parser instead of the standard one.

The parser returns one Instruction object at a time, under the control of the builder, which builds the compiled structure in one pass using recursive descent. Some of the objects returned by the parser are instances of EndInstruction; these are simply tokens. Others are functional objects which the builder initializes and inserts into the compiled structure. For example, when parsing an if-else structure, the parser returns an IfInstruction and at most two EndInstructions (one of type IF_END, and one of type ELSE); only the IfInstruction ends up in the compiled structure. The IfInstruction encapsulates the "else" part of the structure as one of its child lists. The builder must recursively generate these child lists and use them to initialize the IfInstruction, before inserting the IfInstruction into a TemplateLink.

Method overloading is used to identify the subclass of each Instruction so as to determine how it needs to be built. TemplateBuilder declares various buildStatement() methods for building subclasses of Instruction and inserting them into a RuntimeStructuralElement. (LinkedListTemplateBuilder uses TemplateLinks as its RuntimeStructuralElements.) Instruction declares a callBuilder() method, which allows the builder to have an Instruction call it back to identify itself; subclasses of Instruction implement this method as a call to the builder's overloaded buildStatement() method.

Expressions

There is a separate building mechanism for expressions. The executable form of an expression is a tree. The parser is responsible for tokenizing an expression; it then uses the static build method in ExpressionBuilder to build an Expression object (the root node of the expression tree) that can be used at run time. An Expression is essentially an object that can determine its value as a boolean, string, or TemplateModel. It may do this by owning other Expressions; for example, And does a logical AND on two Expressions.

The parser tokenizes an expression into a List of ExpressionElements. Some of these may simply be tokens, such as OpenParen and CloseParen objects, to be be used and eliminated by ExpressionBuilder. Others may be Expression objects representing operators; ExpressionBuilder will initalize these by passing them their operands. ExpressionElements also include Identifiers and StringLiterals, which are simply given as operands to the operators.

ExpressionBuilder first groups any parenthetical sub-expressions into sub-Lists. It then makes repeated passes through the list to associate operators with their operands at each level of operator precedence, recursively building sub-expressions as it goes along. The result is a single Expression.

If you want to add another operator, first make an operator class that implements Binary or Unary (and perhaps extends BooleanExpression or BinaryBoolean). If the operator is only one character long, you can just add a line to the parser's makeElement method to parse it; if it's two characters long, have the parser's init() method store another anonymous inner class in longOpMap to parse it. Then add the operator's class to the static array of arrays of operator classes in ExpressionBuilder, making sure it's in the right place for its precedence level.

Variable is an abstract class for expression objects whose string value is determined by taking the return value of their getAsTemplateModel() method, and trying to read it as a scalar. Subclasses are Identifier (which can look in the root of the data model for its value), Dot (a binary operator that uses its right child's name as a key in the hash pointed to by its left child), DynamicKeyName (a unary postfix operator that's passed an expression by the parser, and uses that expression's string value as a key in the hash pointed to by its operand), and MethodCall (a unary postfix operator that expects its operand to be a TemplateMethodModel).

The index variable of a list structure is stored in TemplateModelRoot while the list is being processed, temporarily hiding any existing variable with the same name. The same goes for arguments to functions.

Functions and Includes

The functions defined in a template are stored at compile time in a HashMap member of the Template object. This is desirable for various reasons (e.g. because it makes it possible for two functions to call each other recursively), but it poses a problem: an include statement can make additional functions available at run time, but Template objects are immutable. The current implementation gets around this problem by allowing functions to be stored either in the Template or in the data model. When an include statement is processed, it retrieves all the functions from the including template and from the included template, wraps each one in a special TemplateModel wrapper called FunctionModel, and puts references to these objects into the root node of the data model, where they can be found by CallInstructions executed in either template.

Exceptions

TemplateModelException is thrown only by classes implementing TemplateModel, and indicates a run-time error. TemplateException is thrown by classes in the template package, to indicate either a run-time or a compile-time error. If a compile-time exception occurs, it bubbles up to the builder, which returns the exception's message in a TemplateTextBlock. The various TemplateProcessors also output exception messages as HTML comments at run time.

GenericEventMulticaster

The package includes a GenericEventMulticaster class that may be generally useful. Unlike AWTEventMulticaster, it doesn't know about any of the EventObjects or EventListeners that it might be used with. When you want to fire an event, you call the GenericEventMulticaster's fireEvent() method, passing it the EventObject you want to fire, and a ListenerAdapter that knows which method to call on each of the listeners. ListenerAdapters can be implemented as anonymous inner classes, as in FileTemplateCache.

Please write to Benjamin Geer at bgeer@freemarker.org if you have questions or suggestions.