You may think that all problems are solved, but in fact, there are still issues with this code. Imagine the following template:
If you compile it, it will fail with:
Cannot find matching method java.lang.Object#toUpperCase(). Please check if the declared type is right and if the method exists.
The reason is that we compile the template statically. While we instructed the compiler that text
is in fact getModel().get("text")
, it is still unable to know what is the return type of this call. Then, it assumes that it returns an Object
, and if you try to call toUpperCase
on an Object
, the method doesn’t exist… This can easily be solved, by making all unresolved method calls dynamic. This means that the template compilation will never throw such errors anymore, but it will instead make a dynamic call. Problem solved.
p(((String)text).toUpperCase())
Then, by adding a cast, the static compiler is able to resolve the method call. Instead of doing a dynamic call, like it would with our extension, it will perform a direct method call, which will be faster! This means that if you add types, by casting, to your template, rendering can be made faster.
This is an interesting idea, but it is not really user friendly. So the last thing I added to the type checking extension is actually an optional, "type checked" mode. If this mode is activated, then the programmer is supposed to tell which are the types of the elements found into the binding. Here, the developper would have to declare that text
is of type String:
import groovy.text.markup.MarkupTemplateEngine
import groovy.text.markup.TemplateConfiguration
def tplConf = new TemplateConfiguration()
MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, tplConf)
def mkpTemplate = engine.createTypeCheckedModelTemplate ''' (1)
p {
yield text.toUpperCase() (2)
}
''', [text:'String']
def model = [text:'Text <to be escaped>'] (3)
mkpTemplate.make(model).writeTo(new PrintWriter(System.out))
1 |
use createTypeCheckedModelTemplate instead of createTemplate |
2 |
you can use text.toUpperCase() without an explicit cast |
3 |
because the model was declared using a simple map |
This mode is actually very interesting if you want to report template errors at template compilation time. Instead of having an error when the template is rendered, the error will occur at compile time. So, for example, if we change the model declaration from:
template compilation will now fail with:
[Static type checking] - Cannot find matching method java.lang.Integer#toUpperCase(). Please check if the declared type is right and if the method exists.
What is really interesting is that you can declare "complex" models, like:
[persons:'List<Person>', posts:[List<Post>]]
and have the template statically compiled! The implementation details of that mode are a bit complex, but you can take a look at the commit if you want to have some hint (don’t hesitate to ask me if you want me to explain how it works).