Checking Types with Build-Ins in FreshMarker

“There are basically two types of people. People who accomplish things, and people who claim to have accomplished things. The first group is less crowded.”

Mark Twain

The FreshMarker Template Engine works with an interpolation system that only takes the type of a variable into account at evaluation time. This approach makes the use of variables in FreshMarker very flexible, but occasionally leads to runtime errors. These can be avoided if the type of a variable can be checked beforehand.

The cause of the runtime errors is the application of a built-in to an unsupported type. In FreshMarker, each built-in is linked to a type. The built-in upper_case can only be applied to the type String. An exception is thrown for all other types. With some interpolations, the template engine is a little more merciful because different types support built-ins with the same name. For example, the types datetime, date, zoneddatetime, yearmonth and year know the built-in year. At this point, it does not matter which of the five types the variable has, a built-in with the name year can be applied to all of them. However, if the variable contains a value of type number or string, an exception is also thrown here.

If the template engine is operated with a clean data model, the assignment of variables to interpolations is usually unambiguous and it is impossible to call an incorrect built-in. Nevertheless, there may be situations in which the assignment is not clean.

To avoid problems, FreshMarker version 1.8.1 offers eight built-ins for type checking. These are is_null, is_string, is_boolean, is_number, is_enum, is_sequence, is_hash and is_range.

The built-ins is_string, is_boolean, is_number and is_enum check whether the value of a variable is a primitive FreshMarker type string, boolean, number or enum. The built-in is_sequence checks for a list, is_hash for a map, bean or record and is_range for a right-limited, right-unlimited or length-limited range. The built-in is_null is only there for completeness and checks a variable value, such as the existence operator for NULL.

In order to offer these built-ins, the usual method for new built-ins had to be abandoned. Up to now, a built-in has been registered for one type. For the built-in is_null, this means that it hat to be registered for every existing type in FreshMarker. It must also be ensured that this built-in is also registered for every type from an extension. All this work not just for one built-in but for at least seven more. A different approach is needed here.

One idea that quickly emerges is to register the built-ins with only the types that should return true. As you can see here with the is_enum built-in registered to the TemplateEnum class.

register.add(TemplateEnum.class, "is_enum", BuiltIn.value(TemplateBoolean.TRUE));

For all other types, a built-in is provided that returns false. This requires changes in the built-in provision code. The following method exists in the ProcessContext class

public BuiltIn getBuiltIn(Class<? extends TemplateObject> type, String name) {
  BuiltIn result = builtIns.get(new BuiltInKey(type, name));
  if (result == null) {
    throw new UnsupportedBuiltInException("unsupported builtin '" + name + "' for " + type.getSimpleName());
  }
  return result;
}

The method receives the type of the variable and the name of the built-in and returns the corresponding BuiltIn implementation from an intenral Map. If no suitable implementation exists, an UnsupportedBuiltInException is thrown.

public BuiltIn getBuiltIn(Class<? extends TemplateObject> type, String name) {
  BuiltIn result = builtIns.get(new BuiltInKey(type, name));
  if (result != null) {
    return result;
  }
  if (TYPE_CHECK_BUILT_INS.contains(name)) {
    return BuiltIn.value(TemplateBoolean.FALSE);
  }
  throw new UnsupportedBuiltInException("unsupported builtin '" + name + "' for " + type.getSimpleName());
}

For the new built-ins, an UnsupportedBuiltInException is no longer thrown, but a dynamic built-in is created, which always returns false.

This completes the implementation of the new type checking buil-ins and they can be used in the new FreshMarker version 1.8.1. Have fun!

Leave a Comment