Skip to content

Under the Hood

revusky edited this page Jun 16, 2024 · 5 revisions

For relatively simple usage, FreeMarker 3 is not very different from older versions of FreeMarker. If your usage pattern is that you expose a tree of maps, lists, and scalars (scalars being strings, numbers, booleans, or dates) to your template without doing anything very fancy, then you might not even notice any difference at all!

(Note, however, that you will surely be confused if you don't understand the new strict variable definition syntax. But if you set legacy_syntax=true at the top of your templates, then you don't even need to deal with that.)

But, in any case, a key difference between this version of FreeMarker and earlier versions is that this version is massively refactored to minimize object wrapping (and unwrapping of course.) By and large, when you expose objects to the template layer in FreeMarker 3, they are exposed directly as POJOs (Plain Old Java Objects.)

Generally speaking, FreeMarker 3 moves towards just using core Java API's. We assume that we can treat something as a sequence (i.e. iterate over its items) if it implements the java.lang.Iterable interface. We can use something as a map (a.k.a. hash) if it is an instance of java.util.Map or you can access its items by indexed offset if it is an instance of java.util.List.

For example, if you use a list literal in FreeMarker 3, like this:

 #set myList = [1, 2, 3, "Joe", true]

then the myList variable is simply an instance of java.util.ArrayList. In general, as before, you can access the list using the intuitive syntax you are alreay familiar with, such as myList[3] or you can iterate over the list as in:

 #list myList as item
    ${item}
 #endlist

Calling Java Methods

Note that in the above example the myList object, being a java.util.ArrayList instance, does have all the methods that the underlying Java object has -- if you really do want or need them! For this, you do need to know about the new :: operator. Here is a simple example of usage:

 ${myList::add(4, "Jane")!}

and then myList would hold:

 [1, 2, 3, "Joe", "Jane", true]

Note that the add(int, Object) method in java.util.List does not actually return a value, so we use the ! to make sure that the interpolation above outputs nothing. A little niggle, though is that the opening and trailing whitespace is not ignored so if you want that line to have no effect on the output, it would need to be:

 ${myList::add(4, "Jane")!}[#t]

Similarly, a plain old java.util.Map (most typically a HashMap though it could be something else) is exposed to the template later directly as a Map (not as a TemplateHashModel or any such wrapper class). So you can write:

 #var unused = myMap::put("foo", "bar")

Note that the preferred way now to write either of the above is:

#exec myList::add(4, "Jane")

or:

#exec myMap::put("foo", "bar")

though, actually, the latter one can now be written:

#set myMap.foo = "bar"

(See here for more detail.)

Note that we use the :: operator to indicate quite specifically that we are invoking a Java method. Thus, if we have a map, foo.bar translates into foo.get("bar"). If we actually do want to invoke a Java method called bar, we use the :: syntax as in: foo::bar(...). The new :: operator, though it adds extra syntax, does finally get rid of this annoying ambiguity between getting subvariables as opposed to actual methods. There is greater clarity.

Note that one rather annoying limitation of older versions of FreeMarker was in dealing with maps with non-string keys. Now the syntax:

foo[bar]

works as one would expect even if bar is not a string type.