3

my use case is to provide the user the possibility to create reports with the help of a template engine. Therefore I extracted the relevant part of my data model and integrated Freemarker as template engine. So far it worked great, but now my data model contains inheritance on some positions - but Freemarker does not seem to support instanceof operations? How to deal with this problem? Are there any other template engines which support inheritance in the model?

Fictive example:

I have 2 classes "Car" and "Bike" which extends "Vehicle". And the model contains a class "vehicle fleet" which contains a list of vehicles. User wants ( with the help of a template) to iterate through the list and write in case of a car the attribute "countSeats", in case of a bike the attribute "frame size". How can this be achieved with Freemarker? Can it be done in any template engine?

Many thanks in advance!

// Edit: Unfortunately it's not possible to split the list with the superclasses in several lists with the 'concrete' objects since the order of vehicles (in the above example) within the list is essential.

Balrog
  • 47
  • 1
  • 6
  • Does freemarker support calling arbitrary java methods on the objects in the list? If so, you could use Object.getClass(); ugly but it works, hopefully there is a better method. – Kasper van den Berg May 23 '15 at 19:47
  • I've never used FreeMarker, but do you think you could replace the need for instanceof using the [visitor pattern](http://stackoverflow.com/questions/29458676/how-to-avoid-instanceof-when-implementing-factory-design-pattern/29459571#29459571)? – Vince May 23 '15 at 19:59
  • @Kasper: It does support calling arbitrary Java methods. – ddekany May 23 '15 at 21:40

4 Answers4

4

Uglier solution

            <#if yourObject.class.simpleName == "Simple class name like String">
                something
            </#if>                     

            <#if yourObject.class.simpleName == "other simple class name">
                do something else
            </#if>  `
ZZ 5
  • 1,744
  • 26
  • 41
3

There's nothing built in for this, but it doesn't have to be either. You can write your own TemplateMethodModelEx, or put plain Java helper objects into the data-model to do pretty much anything. Or, you can just put the relevant classes into the data-model, like root.put("Car", Car.class) etc., and then use the Java API of Class like this: <#if Car.isInstance(someObject)>

ddekany
  • 29,656
  • 4
  • 57
  • 64
  • Thank you very much. But I'm afraid I didn't get your suggestion(s). I got the second one regarding putting Car.class in the model - with this an instanceof check could be done, but I need also a cast to the instance, which will not be possible with this option? The first one regarding writing my own TemplateMethodModelEx - what do you mean with this more in detail? Checked the source but I did not get the idea of your suggestion. – Balrog May 26 '15 at 06:26
  • What do you mean by casting? FTL is dynamically typed, you don't need to do casting. (As of `TemplateMethodModelEx`, it should be apparent from the JavaDoc how to implement it, and then if you put it into the data-model or into a `Configuration`-level shared variable, you can use it in templates like `myMethod(foo, bar)`.) – ddekany May 26 '15 at 20:12
3

Solution using TemplateMethodModelEx.

Class:

public class InstanceOfMethod implements TemplateMethodModelEx {

    @Override
    public Object exec(List list) throws TemplateModelException
    {
        if (list.size() != 2) {
            throw new TemplateModelException("Wrong arguments for method 'instanceOf'. Method has two required parameters: object and class");
        } else {
            Object object = ((WrapperTemplateModel) list.get(0)).getWrappedObject();
            Object p2 = ((WrapperTemplateModel) list.get(1)).getWrappedObject();
            if (!(p2 instanceof Class)) {
                throw new TemplateModelException("Wrong type of the second parameter. It should be Class. Found: " + p2.getClass());
            } else {
                Class c = (Class) p2;
                return c.isAssignableFrom(object.getClass());
            }
        }
    }
}

Put the instance of that class and all required classes to template's input parameters:

parameters.put("instanceOf", new InstanceOfMethod());
parameters.put("Car", Car.class);
...

Or you can add method to shared variables: http://freemarker.org/docs/pgui_config_sharedvariables.html

So you can use the method in FTL as follows:

<#if instanceOf(object, Car)>
       ...
</#if>
o083to
  • 31
  • 2
1

I found this was easier to do than registering all the classes you may potentially try to do in instanceof again:

public class InstanceOfMethod implements TemplateMethodModelEx {

    @Override
    public Object exec(List arguments) throws TemplateModelException {
        if (arguments.size() != 2) {
            throw new TemplateModelException("Wrong arguments");
        }
        
        Object bean = DeepUnwrap.unwrap((TemplateModel) arguments.get(0));
        String className = ((TemplateModel) arguments.get(1)).toString();
        
        try {
            Class clazz = Class.forName(className);
        
            return clazz.isInstance(bean);
        }
        catch (ClassNotFoundException ex) {
            throw new TemplateModelException("Could not find the class '" + className + "'", ex);
        }
    }
    
}

You can then just use a string with the FQN of any class available to the classloader:

<#if instanceOf(object, "mypackage.MyClass")>
...
</#if>
Nathan Crause
  • 874
  • 10
  • 7