Object wrappers
When you add something to a container, it may receive any java object as a parameter, not necessarily a TemplateModel, as you could see in the FreeMarker API. This is because the container implementation can silently replace that object with the appropriate TemplateModel object. For example if you add a String to the container, perhaps it will be replaced with a SimpleScalar instance which stores the same text.
As for when the replacement occurs, it's the business of the container in question (i.e. the business of the class that implements the container interface), but it must happen at the latest when you get the subvariable, as the getter methods (according to the interfaces) return TemplateModel, not Object. For example, SimpleHash, SimpleSequence and SimpleCollection use the laziest strategy; they replace a non-TemplateModel subvariable with an appropriate TemplateModel object when you get the subvariable for the first time.
As for what java objects can be replaced, and with what TemplateModel implementations, it is either handled by the container implementation itself, or it delegates this to an ObjectWrapper instance. ObjectWrapper is an interface that specifies one method: TemplateModel wrap(java.lang.Object obj). You pass in an Object, and it returns the corresponding TemplateModel object, or throws a TemplateModelException if this is not possible. The replacement rules are coded into the ObjectWrapper implementation.
The most important ObjectWrapper implementations that the FreeMarker core provides are:
-
ObjectWrapper.DEFAULT_WRAPPER: It replaces String with SimpleScalar, Number with SimpleNumber, List and array with SimpleSequence, Map with SimpleHash, Boolean with TemplateBooleanModel.TRUE or TemplateBooleanModel.FALSE, W3C DOM nodes with freemarker.ext.dom.NodeModel. For Jython objects, this wrapper will invoke freemarker.ext.jython.JythonWrapper. For all other objects, it will invoke BEANS_WRAPPER.
-
ObjectWrapper.BEANS_WRAPPER: It can expose java Bean properties and other members of arbitrary objects using Java reflection. At least in FreeMarker 2.3, it is a freemarker.ext.beans.BeansWrapper instance; there is a separated chapter about it.
For a concrete example, let's see how the SimpleXxx classes work. SimpleHash, SimpleSequence and SimpleCollection use DEFAULT_WRAPPER to wrap the subvariables (unless you pass in an alternative wrapper in their constructor). So this example demonstrates DEFAULT_WRAPPER in action:
| |||
Assuming that root is the data-model root, the resulting data-model is:
| |||
Note that the Object-s inside theMap and theList are accessible as subvariables too. This is because when you, say, try to access theMap.anotherString, then the SimpleHash (which is used as root hash here) will silently replace the Map (theMap) with a SimpleHash instance that uses the same wrapper as the root hash, so when you try to access the anotherString subvariable of it, it will replace that with a SimpleScalar.
If you drop an ``arbitrary'' object into the data-model, DEFAULT_WRAPPER will invoke BEANS_WRAPPER to wrap the object:
| |||
Assuming this is TestObject:
| |||
The data-model will be:
| |||
So we can merge it with this template:
| |||
Which will output:
| |||
You have seen in earlier examples of this manual that we have used java.util.HashMap as root hash, and not SimpleHash or other FreeMarker specific class. It works because Template.process(...) automatically wraps the object you give as its data-model argument. It uses the object wrapper dictated by the Configuration level setting, object_wrapper (unless you explicitly specify an ObjectWrapper as its parameter). Thus, in simple FreeMarker application you need not know about TemplateModel-s at all. Note that the root need not be a java.util.Map. It can be anything that is wrapped so that it implements the TemplateHashModel interface.
The factory default value of the object_wrapper setting is ObjectWrapper.DEFAULT_WRAPPER. If you want to change it to, say, ObjectWrapper.BEANS_WRAPPER, you can configure the FreeMarker engine (before starting to use it from other threads) like this:
| |||
Note that you can set any object here that implements interface ObjectWrapper, so you can set your custom implementation as well.
For TemplateModel implementations that wrap basic Java container types, as java.util.Map-s and java.util.List-s, the convention is that they use the same object wrapper to wrap their subvariables as their parent container does. Technically correctly said, they are instantiated by their parent container (so it has full control over the creation of them), and the parent container create them so they will use the same object wrapper as the parent itself. Thus, if BEANS_WRAPPER is used for the wrapping of the root hash, it will be used for the wrapping of the subvariables (and the subvariables of the subvariables, etc.) as well. This is exactly the same phenomenon as you have seen with theMap.anotherString earlier.