Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulate null parameters in Freemarker macros

Tags:

freemarker

I'm building a site using Freemarker and have started heavily using macros. I know in Freemarker 2.3 that passing a null value into a macro as a parameter is equivalent to not passing a parameter at all so I've created a global variable called "null" to simulate null checking in my macros:

<#assign null="NUL" />

Now in my macros I can do this:

<#macro doSomething param1=null>
  <#if param1 != null>
    <div>WIN!</div>
  </#if>
</#macro>

The problem comes if I want to pass a parameter that isn't a scalar. For instance, passing a List (which in Freemarker is a SimpleSequence) to a macro and checking against my null keyword yields the error:

freemarker.template.TemplateException: The only legal comparisons are between two numbers, two strings, or two dates. Left hand operand is a freemarker.template.SimpleSequence Right hand operand is a freemarker.template.SimpleScalar

I took a look at the freemarker code and I can see the issue (ComparisonExpression.isTrue()):

if(ltm instanceof TemplateNumberModel && rtm instanceof TemplateNumberModel) { 
  ...
}
else if(ltm instanceof TemplateDateModel && rtm instanceof TemplateDateModel) {
  ...
}
else if(ltm instanceof TemplateScalarModel && rtm instanceof TemplateScalarModel) {
  ...
}
else if(ltm instanceof TemplateBooleanModel && rtm instanceof TemplateBooleanModel) {
  ...
}
// Here we handle compatibility issues
else if(env.isClassicCompatible()) {
  ...
}
else {
  throw new TemplateException("The only legal comparisons...", env);
}

So the only solution I can think of is to set isClassicCompatible to true, which I think will call toString() on both objects and compare the result. However, the documentation specifically says anything relying on old features should be rewritten.

My quesion is, is there a solution to this that doesn't rely on deprecated features?

like image 400
Cameron Avatar asked Jul 01 '11 20:07

Cameron


3 Answers

The null reference is by design an error in FreeMarker. Defining a custom null value - which is a string - is not a good idea for the reasons you mention. The following constructs should be used instead:

  • Macro and function parameters can have a default value, so the callers can omit them
  • To check if a variable is null, you should use the ?? operator: <#if (name??)>
  • When you use a variable that can be null, you should use the ! operator to specify a default value: name!"No name"
  • To check if a sequence (or a string) is empty, use the ?has_content builtin: <#if (names?has_content)>

You can specify an empty sequence as default parameter value in a macro, and simply test whether it's empty.

like image 89
Laurent Pireyn Avatar answered Oct 21 '22 08:10

Laurent Pireyn


Here's what I did, which seems to work in most scenarios:

The default value should be an empty string, and the null-check should be ?has_content.

<#macro someMacro optionalParam="" >
    <#if (optionalParam?has_content)>
        <#-- NOT NULL -->
    <#else>
        <#-- NULL -->
    </#if>
</#macro>
like image 32
Scott Rippey Avatar answered Oct 21 '22 08:10

Scott Rippey


A coworker has suggested a null-checking function may be the most elegant solution:

<#assign null="NUL" />

<#function is_null variable>
  <#if variable?is_string & variable == null>
    <#return true />
  <#else>
    <#return false />
  </#if>
</#function>

Running with that idea, I found another possible solution in the mailing list, which is to create a null-checking function in Java by extending TemplateMethodModelEx. I can then insert it directly into my model map and have it globally available in all my templates. The resulting Freemarker code is pretty clean:

<#maco doSomething param1=null>
  <#if !is_null(param1)>
    <div>WIN!</div>
  </#if>
</#macro>

This seems to be the best way to simulate null parameters inside a macro.

like image 3
Cameron Avatar answered Oct 21 '22 08:10

Cameron