Search code examples
sizefreemarkeriterable

Freemarker - behavior of Built-ins with Iterables


I am observing strange behavior with Freemarker. I have an Iterable in reportModel.affectedJars. It is provided from a graph database - Tinkerpop/Titan.

${iterableHasContent(reportModel.affectedJars)?then("true", "false")}<br>
${reportModel.affectedJars?has_content?then("yes", "no")} has_content<br>
${reportModel.affectedJars?size}<br>
${reportModel.affectedJars}<br>
<#if reportModel.affectedJars?size == 0>
    <p>No archives containing CVE vulnerabilities were found.</p>
<#else>
    <#list reportModel.affectedJars.iterator() as file>
        Something...

iterableHasContent is an alternative of ?size. I would expect this to be quite consistent, in terms of:

false
no has_content
0
<p>No archives ...

But what is actually happening is:

false
yes has_content
6
--- and no iterations. ---

Which looks like there's some glitch that makes Freemarker think that the Iterable is not empty, but then when iterated, it gives no items. No exception is thrown.

I was trying to debug, but the debugger doesn't stop at the relevant code for some reason.

Is there something I am missing in ?size and ?has_content paradigms that allows this behavior?


Solution

  • Don't try do anything with that Iterable, except calling iterator() on it. The problem is that FreeMarker was created before Iterable was introduced, and it sees a pure Iterable (i.e., one which doesn't also implement Collection) as a generic object, rather than some listable thing. (Fixing this without breaking backward compatibility is not possible, though surely there should be a configuration option for it.) This is also where the size 6 comes from; it's the number of items in the hash, which contains its methods and JavaBean properties. Ugh...

    So after you got the Itera*tor*, the next thing you will find that it doesn't support ?size. It supports ?has_content though. (This assumes that you are using the default object wrapper, not pure BeansWrapper, which is pure evil.) But if you can, use #list with a nested #else. Something like:

    <#list reportModel.affectedJars.iterator() as file>
        Something...
    <#else>
        <p>No archives containing CVE vulnerabilities were found.</p>
    </#list>
    

    or a more real life example:

    <#list reportModel.affectedJars.iterator()>
        <p>Found some archives containing CVE vulnerabilities:</p>
        <#items as file>
           Something...
        </#items>
    <#else>
        <p>No archives containing CVE vulnerabilities were found.</p>
    </#list>