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.
|