Models for Arbitrary Java Objects

The freemarker.ext.beans package consists of models that enable any Java object to be used as a TemplateModel. In the very basic case, you only need to call the

public static TemplateModel wrap(Object obj);
        

method of the freemarker.ext.beans.BeansWrapper class. This method will wrap the passed object into an appropriate TemplateModel. Below is a summary of the properties of returned model wrappers. Let's assume that the model that resulted from the BeansWrapper call on object obj is named model in the template model root for the sake of the following discussion.

TemplateHashModel functionality

Every object will be wrapped into a TemplateHashModel that will expose JavaBeans properties and methods of the object. This way, you can use model.foo in the template to invoke obj.getFoo() or obj.isFoo() methods. Public methods are also retrievable through the hash model as template method models, therefore you can use the model.doBar() to invoke object.doBar(). More on this on discussion of method model functionality. The hash model for native Java arrays also exposes the "length" key that retrieves the length of the array as an Integer-holding TemplateNumberModel.

If the requested key can not be mapped to a bean property or method, the framework will attempt to locate the so-called "generic get method", that is a method with signature public [any-return-type] get(String) or public [any-return-type] get(Object) and invoke that method with the requested key. Note that this allows convenient access to mappings in a java.util.Map and similar classes - as long as the keys of the map are Strings and some property or method name does not shadow the mapping. (There is a solution to avoid shadowing, read on.) Also note that the models for java.util.ResourceBundle objects use the getObject(String) as the generic get method.

TemplateScalarModel functionality

Every object's model wrapper will implement TemplateScalarModel whose getAsString() method simply delegates to toString(). Note that wrapping String objects into Bean wrappers provides much more functionality than just them being scalars: because of the hash interface described above, the models that wrap Strings also provide access to all String methods (indexOf, substring, etc.).

TemplateNumberModel functionality

Model wrappers for objects that are instances of java.lang.Number implement TemplateNumberModel whose getAsNumber() method returns the wrapped number object. Note that wrapping Number objects into Bean wrappers provides much more functionality than just them being number models: because of the hash interface described above, the models that wrap Numbers also provide access to all their methods

TemplateCollectionModel functionality

Model wrappers for native Java arrays and all classes that implement java.util.Collection will implement TemplateCollectionModel and thus gain the additional capability of being listable through <list> and <foreach> blocks.

TemplateSequenceModel functionality

Model wrappers for native Java arrays and all classes that implement java.util.List will implement TemplateSequenceModel and thus their elements will be accessible by index using the model[i] syntax. Also, every method that takes a single parameter that is assignable through reflective method invocation from a java.lang.Integer (these are int, long, float, double, java.lang.Object, java.lang.Number, and java.lang.Integer) also implements this interface. This way, you have a convenient way for accessing indexed bean properties: model.foo[i] will translate into obj.getFoo(i).

TemplateMethodModel functionality

All methods of an object are represented as TemplateMethodModelEx objects accessible using a hash key with method name on their object's model. When you call a method using model.method(arg1, arg2, ...) the arguments are passed to the method as template models. The method will first try to unwrap them. That is, every passed model that is a Beans wrapper will be unwrapped to its underlying object. Furthermore, every TemplateNumberModel will be unwrapped to its number value and every TemplateScalarModel will be unwrapped to its string value. These unwrapped arguments are then used for the actual method call. In case the method is overloaded, the most specific method will be selected using the same rules that are used by the Java compiler to select a method from several overloaded methods. In case that no method signature matches the passed parameters, or that no method can be chosen without ambiguity, a TemplateModelException is thrown.

Specifically note one peculiarity: All numbers that are specified as literals in the template, or are a result of arithmetic calculations in the template will be internally represented by the template engine as instances of the Java's built in arbitrary-precision arithmetic class, java.math.BigDecimal. While this provides for arbitrary-precision arithmetic in the templates, it can create unusual effects, since if you try to invoke a method with signature someMethod(int i) using the expression model.someMethod(1), you'll be surprised that the method is not found. This is due to the fact that that literal 1 is in fact a BigDecimal, so it is not assignable to primitive int type and therefore the method is not called. Fortunately, to explicitly force the internal representation of numbers to some other type, you can use the syntax numericExpression?type where type can be a name of any primitive Java number type (byte, short, int, long, float, double) as well as string for converting the number into string. So, the above template snippet would invoke the correct method if you would write model.someMethod(1?int). Note also that you needn't do all of this when calling non-overloaded methods as the underlying method model for non-overloaded methods will correctly convert any passed BigDecimal to the appropriate parameter type. In some later release, we might find a satisfactory solution for this problem for overloaded methods as well.

Models for instances of java.util.Map also implement TemplateMethodModelEx as a means for invoking their get() method. As it was discussed previously, you can use the hash functionality to access the "get" method as well, but it has several drawbacks: it's slower because first property and method names are checked for the key; keys that conflict with property and method names will be shadowed by them; finally you can use String keys only with that approach. In contrast, invoking model(key) translates to model.get(key) directly: it's faster because there's no property and method name lookup; it is subject to no shadowing; and finally it works for non-String keys since the argument is unwrapped just as with ordinary method calls. In effect, model(key) on a Map is equal to model.get(key), only shorter to write.

Models for java.util.ResourceBundle also implement TemplateMethodModelEx as a convenient way of resource access and message formatting. A single-argument call to a bundle will retrieve the resource with the name that corresponds to the toString() value of the unwrapped argument. A multiple-argument call to a bundle will also retrieve the resource with the name that corresponds to the toString() value of the unwrapped argument, but it will use it as a format pattern and pass it to java.text.MessageFormat using the unwrapped values of second and later arguments as formatting parameters.

Accessing static methods

The class freemarker.ext.beans.StaticModel is used to create models for static methods and fields of a class. Just call

public static TemplateModel create(Class clazz);
        

And you will get a template hash model that exposes all static methods and static fields (both final and non-final) as hash keys. Suppose that you put the following model in your root model:

root.put("File", StaticModel.create("java.io.File"));
        

From now on, you can use ${File.SEPARATOR} to insert the file separator character into your template, or you can even list all roots of your file system by:

<list File.listRoots() as root>...</list>
	

Of course, you must be aware of the potential security issues this model brings.

You can even give the template authors complete freedom over which classes' static methods they use by placing the singleton instance of freemarker.ext.beans.StaticModels into your template root model with

root.put("statics", StaticModels.INSTANCE);
        

This object exposes just about any class' static methods if it's used as a hash with class name as the key. What about ${statics["java.lang.System"].currentTimeMillis()}? Note, however that this has even more security implications, as someone could even invoke System.exit() using this model.