-
Notifications
You must be signed in to change notification settings - Fork 3
Under the Hood
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
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.